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.ui.controls;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.jface.dialogs.Dialog;
  import org.eclipse.jface.dialogs.IDialogSettings;
  import org.eclipse.jface.viewers.ArrayContentProvider;
  import org.eclipse.jface.viewers.CheckStateChangedEvent;
  import org.eclipse.jface.viewers.CheckboxTableViewer;
  import org.eclipse.jface.viewers.ICheckStateListener;
  import org.eclipse.jface.viewers.IElementComparer;
  import org.eclipse.jface.viewers.ISelectionChangedListener;
  import org.eclipse.jface.viewers.IStructuredSelection;
  import org.eclipse.jface.viewers.SelectionChangedEvent;
  import org.eclipse.jface.viewers.TableViewerColumn;
  import org.eclipse.jface.viewers.Viewer;
  import org.eclipse.jface.viewers.ViewerSorter;
  import org.eclipse.swt.SWT;
  import org.eclipse.swt.custom.SashForm;
  import org.eclipse.swt.events.SelectionAdapter;
  import org.eclipse.swt.events.SelectionEvent;
  import org.eclipse.swt.graphics.Font;
  import org.eclipse.swt.layout.GridData;
  import org.eclipse.swt.layout.GridLayout;
  import org.eclipse.swt.widgets.Button;
  import org.eclipse.swt.widgets.Composite;
  import org.eclipse.swt.widgets.Control;
  import org.eclipse.swt.widgets.Label;
  import org.eclipse.swt.widgets.TabFolder;
  import org.eclipse.swt.widgets.TabItem;
  import org.eclipse.swt.widgets.Table;
  
  import tern.eclipse.ide.core.IIDETernProject;
  import tern.eclipse.ide.core.TernCorePlugin;
  import tern.eclipse.ide.internal.ui.TernUIMessages;
  import tern.eclipse.ide.internal.ui.Trace;
  import tern.eclipse.ide.internal.ui.controls.DependenciesPanel;
  import tern.eclipse.ide.internal.ui.controls.DetailsPanel;
  import tern.eclipse.ide.internal.ui.controls.OptionsPanel;
  import tern.eclipse.ide.internal.ui.viewers.TernModuleVersionEditingSupport;
  import tern.eclipse.ide.ui.TernUIPlugin;
  import tern.eclipse.ide.ui.viewers.TernModuleLabelProvider;
  import tern.metadata.TernModuleMetadata;
  import tern.repository.ITernRepository;
  import tern.server.ITernModule;
  import tern.server.ITernModuleConfigurable;
  import tern.utils.StringUtils;
  import tern.utils.TernModuleHelper;
  
  /**
   * Block to select Tern plugins + JSON Type Definitions.
   * 
   */
  public class TernModulesBlock extends AbstractTableBlock {
  
  	private static final IElementComparer TERN_MODULES_COMPARER = new IElementComparer() {
  
  		@Override
  		public boolean equals(Object a, Object b) {
  			return a.equals(b);
  		}
  
  		@Override
  		public int hashCode(Object element) {
  			return element.hashCode();
  		}
  	};
  
  	private final String tableLabel;
  	private final IProject project;
  
  	private final Map<String, ITernModule> ternModules = new HashMap<String, ITernModule>();
  	private CheckboxTableViewer tableViewer;
  	private DetailsPanel detailsPanel;
  	private DependenciesPanel dependenciesPanel;
  	private OptionsPanel optionsPanel;
  	private TabItem optionsTabItem;
  	private TabFolder tabFolder;
  	private TabItem detailsTabItem;
  	private Button selectDependenciesCheckbox;
  
  	private Collection<ITernModule> checkedModules;
  	private boolean checkUpdating;
  
  	public TernModulesBlock(IProject project, String tableLabel) {
  		this.project = project;
  		this.tableLabel = tableLabel;
  	}
  
  	public Control createControl(Composite ancestor) {
  
  		Composite parent = new Composite(ancestor, SWT.NULL);
  		GridLayout layout = new GridLayout();
  		layout.numColumns = 1;
  		layout.marginHeight = 0;
  		layout.marginWidth = 0;
  		parent.setLayout(layout);
  		Font font = ancestor.getFont();
  		parent.setFont(font);
  
  		GridData data;
  		if (tableLabel != null) {
  			Composite header = new Composite(parent, SWT.NONE);
  			data = new GridData();
  			data.horizontalSpan = 2;
  			header.setLayoutData(data);
  			header.setLayout(new GridLayout(2, false));
  
  			// Create description
  			Label tableLabel = new Label(header, SWT.NONE);
  			tableLabel.setText(this.tableLabel);
  			tableLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
  			tableLabel.setFont(font);
  			// Create dependencies checkbox
  			selectDependenciesCheckbox = new Button(header, SWT.CHECK);
  			selectDependenciesCheckbox.setSelection(true);
  		}
  
  		SashForm sashForm = new SashForm(parent, SWT.HORIZONTAL | SWT.SMOOTH);
  		data = new GridData(SWT.FILL, SWT.FILL, true, true);
  		sashForm.setLayoutData(data);
  
  		createModulesMaster(sashForm);
  		createModulesDetails(sashForm);
  
  		Dialog.applyDialogFont(parent);
  		return parent;
  	}
  
  	/**
  	 * Create table of tern modules.
  	 * 
  	 * @param ancestor
  	 */
  	private void createModulesMaster(Composite ancestor) {
  		Composite parent = new Composite(ancestor, SWT.NULL);
  		GridLayout layout = new GridLayout();
  		layout.numColumns = 2;
  		layout.marginHeight = 0;
  		layout.marginWidth = 0;
  		parent.setLayout(layout);
  		Font font = ancestor.getFont();
  		parent.setFont(font);
  
  		Table table = new Table(parent, SWT.CHECK | SWT.BORDER
  				| SWT.FULL_SELECTION | SWT.V_SCROLL);
  
  		GridData data = new GridData(GridData.FILL_BOTH);
  		data.widthHint = 350;
  		data.heightHint = 400;
  		table.setLayoutData(data);
  		table.setFont(parent.getFont());
  
  		table.setHeaderVisible(true);
  		table.setLinesVisible(true);
  
  		tableViewer = new CheckboxTableViewer(table);
  
  		// create name column
  		TableViewerColumn nameColumn = new TableViewerColumn(tableViewer,
  				SWT.NONE);
  		nameColumn.getColumn().setWidth(180);
  		nameColumn.getColumn().setResizable(true);
  		nameColumn.getColumn().setText(
  				TernUIMessages.TernModulesBlock_moduleName);
  		nameColumn.getColumn().addSelectionListener(new SelectionAdapter() {
  			@Override
  			public void widgetSelected(SelectionEvent e) {
  				sortByName();
  			}
  		});
  
  		// create version column
  		TableViewerColumn versionColumn = new TableViewerColumn(tableViewer,
  				SWT.NONE);
  		versionColumn.getColumn().setWidth(100);
  		versionColumn.getColumn().setResizable(true);
  		versionColumn.getColumn().setText(
  				TernUIMessages.TernModulesBlock_moduleVersion);
  		final TernModuleVersionEditingSupport versionEditiongSupport = new TernModuleVersionEditingSupport(
  				tableViewer);
  		versionColumn.setEditingSupport(versionEditiongSupport);
  
  		tableViewer.setComparer(TERN_MODULES_COMPARER);
  		tableViewer.setLabelProvider(TernModuleLabelProvider.getInstance());
  		tableViewer.setContentProvider(ArrayContentProvider.getInstance());
  
  		// when a module is checked and dependencies checkbox is checked, tern
  		// module dependencies must be selected too
  		addCheckStateListener(new ICheckStateListener() {
  
  			@Override
  			public void checkStateChanged(CheckStateChangedEvent e) {
  				if (checkUpdating) {
  					return;
  				}
  				try {
  					checkUpdating = true;
  					ITernModule module = ((ITernModule) e.getElement());
  					// update checked modules list
  					updateCheckedModules(module, e.getChecked());
  					if (e.getChecked() && isSelectDependencies()) {
  						TernModuleMetadata metadata = module.getMetadata();
  						if (metadata != null) {
  							ITernModule dependencyModule = null;
  							// loop for each dependencies and check it if needed
  							for (String moduleName : metadata
  									.getDependencies(module.getVersion())) {
  								dependencyModule = ternModules.get(moduleName);
  								if (dependencyModule != null) {
  									// update module selection
  									if (!tableViewer
  											.getChecked(dependencyModule)) {
  										tableViewer.setChecked(
  												dependencyModule, true);
  									}
  									// update checked modules list
  									updateCheckedModules(dependencyModule, true);
  									if (dependencyModule instanceof ITernModuleConfigurable) {
  										ITernModuleConfigurable configurable = (ITernModuleConfigurable) dependencyModule;
  										if (configurable.hasVersion()) {
  											// update version
  											String version = configurable
  													.getModule(moduleName)
  													.getVersion();
  											versionEditiongSupport.setValue(
  													dependencyModule, version);
  										}
  									}
  								}
  							}
  						}
  					}
  				} finally {
  					checkUpdating = false;
  				}
  			}
  		});
  
  		// when a module is selected, details, dependencies, options tabs must
  		// be
  		// refreshed.
  		addSelectionChangedListener(new ISelectionChangedListener() {
  
  			@Override
  			public void selectionChanged(SelectionChangedEvent e) {
  				if (!e.getSelection().isEmpty()) {
  					ITernModule module = (ITernModule) ((IStructuredSelection) e
  							.getSelection()).getFirstElement();
  					refreshModule(module);
  				} else {
  					refreshModule(null);
  				}
  			}
  		});
  		restoreColumnSettings();
  	}
  
  	private void createModulesDetails(Composite parent) {
  
  		// Create tab folder.
  		tabFolder = new TabFolder(parent, SWT.NONE);
  		GridData data = new GridData(GridData.FILL_HORIZONTAL);
  		data.heightHint = 80;
  		tabFolder.setLayoutData(data);
  
  		// create details tab
  		this.detailsPanel = new DetailsPanel(tabFolder, project);
  		detailsTabItem = new TabItem(tabFolder, SWT.NULL);
  		detailsTabItem.setControl(this.detailsPanel);
  		detailsTabItem.setText(TernUIMessages.TernModulesBlock_detailsTabLabel);
  
  		// create dependencies tab
  		this.dependenciesPanel = new DependenciesPanel(tabFolder, project);
  		TabItem dependenciesTabItem = new TabItem(tabFolder, SWT.NULL);
  		dependenciesTabItem.setControl(this.dependenciesPanel);
  		dependenciesTabItem
  				.setText(TernUIMessages.TernModulesBlock_dependenciesTabLabel);
  
  		// create options panel.
  		this.optionsPanel = new OptionsPanel(tabFolder, project);
  	}
  
  	/**
  	 * Refresh tab items with the given module information
  	 * 
  	 * @param module
  	 */
  	private void refreshModule(ITernModule module) {
  		if (TernModuleHelper.hasOptions(module)) {
  			// module has options, create options tab if needed
  			if (optionsTabItem == null) {
  				this.optionsTabItem = new TabItem(tabFolder, SWT.NULL);
  				optionsTabItem.setControl(this.optionsPanel);
  				optionsTabItem
  						.setText(TernUIMessages.TernModulesBlock_optionsTabLabel);
  			}
  			optionsPanel.refresh(module);
  
  		} else {
  			// module has no options, don't display options tab
  			if (optionsTabItem != null) {
  				optionsTabItem.dispose();
  			}
  			optionsTabItem = null;
  		}
  
  		detailsPanel.refresh(module);
  		dependenciesPanel.refresh(module);
  		// select the details tab.
  		tabFolder.setSelection(detailsTabItem);
  	}
  
  	public void addSelectionChangedListener(ISelectionChangedListener listener) {
  		tableViewer.addSelectionChangedListener(listener);
  	}
  
  	public void removeSelectionChangedListener(
  			ISelectionChangedListener listener) {
  		tableViewer.removeSelectionChangedListener(listener);
  	}
  
  	public void addCheckStateListener(ICheckStateListener listener) {
  		tableViewer.addCheckStateListener(listener);
  	}
  
  	public void removeCheckStateListener(ICheckStateListener listener) {
  		tableViewer.removeCheckStateListener(listener);
  	}
  
  	/**
  	 * Sorts by name.
  	 */
  	private void sortByName() {
  		tableViewer.setSorter(new ViewerSorter() {
  			@Override
  			public int compare(Viewer viewer, Object e1, Object e2) {
  				if ((e1 instanceof ITernModule) && (e2 instanceof ITernModule)) {
  					ITernModule left = (ITernModule) e1;
  					ITernModule right = (ITernModule) e2;
  					return getNameOrLabel(left).compareToIgnoreCase(
  							getNameOrLabel(right));
  				}
  				return super.compare(viewer, e1, e2);
  			}
  
  			private String getNameOrLabel(ITernModule module) {
  				TernModuleMetadata metadata = module.getMetadata();
  				if (metadata == null) {
  					return module.getName();
  				}
  				return StringUtils.isEmpty(metadata.getLabel()) ? module
  						.getName() : metadata.getLabel();
  			}
  
  			@Override
  			public boolean isSorterProperty(Object element, String property) {
  				return true;
  			}
  		});
  	}
  
  	public void setTernModules(ITernModule[] modules) {
  		ternModules.clear();
  		for (ITernModule module : modules) {
  			if (module instanceof ITernModuleConfigurable) {
  				ITernModuleConfigurable configurable = (ITernModuleConfigurable) module;
  				if (!configurable.hasVersion()) {
  					ternModules.put(module.getName(), module);
  				} else {
  					Collection<ITernModule> mods = configurable.getModules();
  					for (ITernModule mod : mods) {
  						ternModules.put(mod.getName(), module);
  					}
  				}
  			} else {
  				ternModules.put(module.getName(), module);
  			}
  		}
  		tableViewer.setInput(modules);
  	}
  
  	public Collection<ITernModule> getCheckedModules() {
  		if (checkedModules != null) {
  			return checkedModules;
  		}
  		return Collections.emptyList();
  	}
  
  	public void setCheckedModules(Collection<ITernModule> checkedModules) {
  		this.checkedModules = checkedModules;
  		tableViewer.setCheckedElements(checkedModules.toArray());
  	}
  
  	public void setCheckedModule(ITernModule module, boolean selected) {
  		updateCheckedModules(module, selected);
  		tableViewer.setChecked(module, selected);
  	}
  
  	private void updateCheckedModules(ITernModule module, boolean checked) {
  		if (checked) {
  			if (!checkedModules.contains(module)) {
  				checkedModules.add(module);
  			}
  		} else {
  			checkedModules.remove(module);
  		}
  	}
  
  	@Override
  	protected void setSortColumn(int column) {
  		switch (column) {
  		case 1:
  			sortByName();
  			break;
  		}
  		super.setSortColumn(column);
  	}
  
  	@Override
  	protected Table getTable() {
  		return tableViewer.getTable();
  	}
  
  	@Override
  	protected IDialogSettings getDialogSettings() {
  		return TernUIPlugin.getDefault().getDialogSettings();
  	}
  
  	@Override
  	protected String getQualifier() {
  		return TernUIPlugin.PLUGIN_ID + ".modules.";
  	}
  
  	/**
  	 * Load plugins from tern project.
  	 * 
  	 * @param moduleNames
  	 * @param repository
  	 */
  	public void loadModules() {
  		try {
  
  			// load modules from the given tern project
  			IIDETernProject ternProject = getTernProject();
  			if (ternProject != null) {
  
  				// Add list of tern modules from the repository and local
  				List<ITernModule> allModules = ternProject.getAllModules();
  				// Group by type
  				allModules = TernModuleHelper.groupByType(allModules);
  
  				// checked modules
  				List<ITernModule> checkedModules = TernCorePlugin
  						.getTernRepositoryManager().getCheckedModules(
  								ternProject, allModules);
  				refresh(allModules, checkedModules);
  				/*
  				 * if (checkedModules.size() > 0) { ITernModule firstModule =
  				 * checkedModules.get(0); tableViewer.setSelection(new
  				 * StructuredSelection( firstModule)); }
  				 */
  			}
  
  		} catch (Throwable e) {
  			Trace.trace(Trace.SEVERE, "Error while loading plugins.", e);
  		}
  	}
  
  	/**
  	 * Load modules from the given tern repository and check the modules with
  	 * the given checked module names.
  	 * 
  	 * @param repository
  	 *            tern repository which hosts tern modules.
  	 * @param checkedModuleNames
  	 *            module names to check.
  	 */
  	public void loadModules(ITernRepository repository,
  			String[] checkedModuleNames) {
  		try {
  			// load modules from the given repository
  			List<ITernModule> allModules = new ArrayList<ITernModule>(
  					Arrays.asList(repository.getModules()));
  			// Group by type
  			List<ITernModule> groupedModules = TernModuleHelper
  					.groupByType(allModules);
  			// checked modules
  			List<ITernModule> checkedModules = TernCorePlugin
  					.getTernRepositoryManager().getCheckedModules(
  							checkedModuleNames, allModules, groupedModules);
  			refresh(groupedModules, checkedModules);
  		} catch (Throwable e) {
  			Trace.trace(Trace.SEVERE, "Error while loading plugins.", e);
  		}
  	}
  
  	public void refresh(Collection<ITernModule> allModules,
  			Collection<ITernModule> checkedModules) {
  		this.setTernModules(allModules.toArray(ITernModule.EMPTY_MODULE));
  		this.setCheckedModules(checkedModules);
  	}
  
  	/**
  	 * Returns true if tern modules dependencies must be select when a tern
  	 * module is selected and false otherwise.
  	 * 
  	 * @return true if tern modules dependencies must be select when a tern
  	 *         module is selected and false otherwise.
  	 */
  	private boolean isSelectDependencies() {
  		return selectDependenciesCheckbox != null
  				&& selectDependenciesCheckbox.getSelection();
  	}
  
  	/**
  	 * Returns the tern project and null otherwise.
  	 * 
  	 * @return the tern project and null otherwise.
  	 * 
  	 * @throws CoreException
  	 */
  	public IIDETernProject getTernProject() throws CoreException {
  		return project != null ? TernCorePlugin.getTernProject(project) : null;
  	}
  
  	/**
  	 * Returns the eclipse project and null otherwise.
  	 * 
  	 * @return the eclipse project and null otherwise.
  	 */
  	public IProject getProject() {
  		return project;
  	}
  
  	public void setEnabled(boolean enabled) {
  		getTable().setEnabled(enabled);
  	}
  
  	public boolean isCheckUpdating() {
  		return checkUpdating;
  	}
  
  }
  