/*******************************************************************************
 * Copyright (c) 2007, 2013 Intel Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Intel Corporation - initial API and implementation
 *     IBM Corporation
 *     James Blackburn (Broadcom Corp.)
 *     Serge Beauchamp (Freescale Semiconductor) - Bug 406545
 *******************************************************************************/
package org.eclipse.cdt.ui.newui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICResourceDescription;
import org.eclipse.cdt.internal.ui.newui.Messages;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.core.resources.IProject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

/**
 * @noextend This class is not intended to be subclassed by clients.
 */
public class RefsTab extends AbstractCPropertyTab {

	/** gray colour for 'disabled' items */
	private final Color GRAY_COLOR = new Color(Display.getDefault(), 100, 100, 100);
	public Composite comp;
	private Tree tree;

	static private final String ACTIVE = "[" + Messages.RefsTab_Active + "]"; //$NON-NLS-1$ //$NON-NLS-2$

	private static final int EXPAND_ALL_BUTTON = 0;
	private static final int COLLAPSE_ALL_BUTTON = 1;
	private static final int MOVEUP_BUTTON = 3;
	private static final int MOVEDOWN_BUTTON = 4;

	@Override
	public void createControls(Composite parent) {
		super.createControls(parent);
		initButtons(new String[] { Messages.RefsTab_ExpandAll, Messages.RefsTab_CollapseAll, null, MOVEUP_STR,
				MOVEDOWN_STR }, 120);
		usercomp.setLayout(new GridLayout(1, false));

		tree = new Tree(usercomp, SWT.SINGLE | SWT.CHECK | SWT.BORDER);
		tree.setLayoutData(new GridData(GridData.FILL_BOTH));
		tree.getAccessible().addAccessibleListener(new AccessibleAdapter() {
			@Override
			public void getName(AccessibleEvent e) {
				e.result = Messages.RefsTab_ProjectsList;
			}
		});

		// Populate the tree
		initData();
		tree.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				if ((e.detail & SWT.CHECK) == SWT.CHECK && e.item != null && (e.item instanceof TreeItem)) {
					TreeItem sel = (TreeItem) e.item;
					Object data = sel.getData();

					// If data is not a configuration ID, then the user isn't allowed to select this...
					if (data == null) {
						sel.setChecked(false);
						return;
					}

					boolean checked = sel.getChecked();
					TreeItem parent = sel.getParentItem();

					if (parent == null) {
						// Project -- top-level item -- selected
						if (checked)
							sel.setExpanded(true);
						for (TreeItem child : sel.getItems()) {
							if (checked) {
								// Don't select a non-allowed configuration
								if (child.getData() != null) {
									child.setChecked(true);
									break;
								}
							} else
								child.setChecked(false);
						}
					} else {
						// Configuration selected (it has a parent)
						if (parent.getChecked()) {
							// Deselect other configs already selected
							for (TreeItem obj : parent.getItems())
								obj.setChecked(false);
							sel.setChecked(checked);
						}
						parent.setChecked(checked);
					}

					// Save the checked configurations
					saveChecked();
				}
				updateButtons();
			}
		});

		tree.addTreeListener(new TreeListener() {
			@Override
			public void treeCollapsed(TreeEvent e) {
				updateExpandButtons(e, false, true);
			}

			@Override
			public void treeExpanded(TreeEvent e) {
				updateExpandButtons(e, true, false);
			}
		});

	}

	@Override
	public void buttonPressed(int n) {
		switch (n) {
		case COLLAPSE_ALL_BUTTON:
		case EXPAND_ALL_BUTTON:
			for (TreeItem item : tree.getItems())
				item.setExpanded(n == EXPAND_ALL_BUTTON);
			updateButtons();
			break;
		case MOVEUP_BUTTON:
		case MOVEDOWN_BUTTON:
			// TODO cache this...
			Map<String, String> oldMapping = getResDesc().getConfiguration().getReferenceInfo();
			TreeItem ti = tree.getSelection()[0];
			String projectName = ti.getText();
			List<String> projNames = new ArrayList<>(oldMapping.keySet());
			int index = projNames.indexOf(projectName);
			if (n == MOVEUP_BUTTON) {
				if (index > 0) {
					projNames.set(index, projNames.get(index - 1));
					projNames.set(index - 1, projectName);
				}
			} else {
				if (index < projNames.size() - 1) {
					projNames.set(index, projNames.get(index + 1));
					projNames.set(index + 1, projectName);
				}
			}
			Map<String, String> newMapping = new LinkedHashMap<>(oldMapping.size());
			for (String name : projNames)
				newMapping.put(name, oldMapping.get(name));
			getResDesc().getConfiguration().setReferenceInfo(newMapping);
			initData();
			break;
		}
	}

	@Override
	protected void updateData(ICResourceDescription cfgd) {
		if (page.isMultiCfg()) {
			setAllVisible(false, null);
		} else {
			if (!usercomp.isVisible())
				setAllVisible(true, null);
			initData();
		}
	}

	@Override
	public boolean canSupportMultiCfg() {
		return false;
	}

	/**
	 * Persist the checked configurations
	 */
	private void saveChecked() {
		Map<String, String> refs = new LinkedHashMap<>();
		for (TreeItem project : tree.getItems()) {
			if (project.getChecked()) {
				if (project.getData() instanceof String) {
					assert (project.getData() != null);
					// Project is missing from the workspace, maintain references
					refs.put(project.getText(), (String) project.getData());
				} else {
					for (TreeItem config : project.getItems()) {
						if (config.getChecked()) {
							assert (config.getData() != null);
							refs.put(project.getText(), (String) config.getData());
							break; // only one configuration can be selected a time in a project
						}
					}
				}
			}
		}
		getResDesc().getConfiguration().setReferenceInfo(refs);
	}

	/**
	 * Initialises the tree.
	 *
	 * TreeItems are either
	 * TI:       Text            ,     Data
	 *   {IProject.getName()}    , {IProject}
	 *       {cfgName}           , {String cfgId}
	 *       {cfgName}           , {null}  // config is not allowed to be selected
	 *
	 * If the projects doesn't exist in the workspace:
	 *   {IProject.getName()}    , {String cfgId}
	 *
	 */
	private void initData() {
		// Persist the current select / expand state to restore...
		String currentSelection = tree.getSelectionCount() == 1 ? tree.getSelection()[0].getText() : null;
		Set<String> currentExpanded = new HashSet<>();
		for (TreeItem ti : tree.getItems())
			if (ti.getExpanded())
				currentExpanded.add(ti.getText());

		tree.removeAll();
		IProject p = page.getProject();
		if (p == null)
			return;

		// Get all the CDT references
		Map<String, String> refs = getResDesc().getConfiguration().getReferenceInfo();

		// Preserve project order. All linked to projects occur before others
		Set<String> projects = new LinkedHashSet<>(refs.keySet());
		for (IProject prj : p.getWorkspace().getRoot().getProjects())
			projects.add(prj.getName());

		for (String pname : projects) {
			// The referenced configuration ID
			String ref = refs.get(pname);
			IProject prj;
			ICConfigurationDescription[] cfgs;
			try {
				prj = p.getWorkspace().getRoot().getProject(pname);
				cfgs = page.getCfgsReadOnly(prj);
			} catch (Exception e) {
				CUIPlugin.log(Messages.RefsTab_ConfigurationsAccessError + pname, e);
				continue;
			}
			if (cfgs == null || cfgs.length == 0) {
				// If the project is referenced, then make sure the user knows about it!
				if (ref != null) {
					TreeItem ti = new TreeItem(tree, SWT.NONE);
					ti.setChecked(true);
					ti.setText(pname);
					ti.setData(refs.get(pname));
					ti.setForeground(GRAY_COLOR);
				}
				continue;
			}

			// Only show the current project if it's got more than 1 configuration
			if (page.getProject().equals(prj) && cfgs.length < 2)
				continue;

			// Add the project
			TreeItem ti = new TreeItem(tree, SWT.NONE);
			ti.setText(pname);
			ti.setData(prj);
			if (ref != null)
				ti.setChecked(true);

			// Add the configurations
			TreeItem ti1;
			if (!prj.equals(p)) {
				// [ Active ] config in the tree
				ti1 = new TreeItem(ti, SWT.NONE);
				ti1.setText(ACTIVE);
				ti1.setData(EMPTY_STR);
				if (EMPTY_STR.equals(ref)) {
					ti1.setChecked(true);
					ti1.setData(ref);
				}
			}
			// Name configurations in the tree
			for (ICConfigurationDescription cfg : cfgs) {
				// Don't include self configuration
				ti1 = new TreeItem(ti, SWT.NONE);
				ti1.setText(cfg.getName());
				if (prj.equals(p) && cfg.getId().equals(page.getResDesc().getConfiguration().getId())) {
					// users may *only* reference other configuration in the project
					// this data is deliberately null as it may not be selected...
					ti1.setForeground(GRAY_COLOR);
					continue;
				} else if (cfg.getId().equals(ref))
					ti1.setChecked(true);
				ti1.setData(cfg.getId());
			}
		}

		// Reselect / Re-expand previously selected & expanded items
		if (currentSelection != null)
			for (TreeItem ti : tree.getItems())
				if (ti.getText().equals(currentSelection)) {
					tree.setSelection(ti);
					break;
				}
		for (TreeItem ti : tree.getItems())
			if (currentExpanded.contains(ti.getText()))
				ti.setExpanded(true);

		updateButtons();
	}

	@Override
	protected void performApply(ICResourceDescription src, ICResourceDescription dst) {
		dst.getConfiguration().setReferenceInfo(src.getConfiguration().getReferenceInfo());
	}

	// This page can be displayed for project only
	@Override
	public boolean canBeVisible() {
		return page.isForProject();
	}

	@Override
	protected void performDefaults() {
		if (!usercomp.isVisible())
			return;
		getResDesc().getConfiguration().setReferenceInfo(new HashMap<String, String>());
		initData();
	}

	@Override
	protected void updateButtons() {
		updateExpandButtons(null, false, false);
		updateMoveButtons();
	}

	@Override
	public void dispose() {
		super.dispose();
		GRAY_COLOR.dispose();
	}

	private void updateExpandButtons(TreeEvent e, boolean stateE, boolean stateC) {
		boolean cntE = stateE;
		boolean cntC = stateC;
		for (TreeItem item : tree.getItems()) {
			if (e != null && e.widget.equals(item))
				continue;
			if (item.getExpanded())
				cntE = true;
			else
				cntC = true;
		}
		buttonSetEnabled(EXPAND_ALL_BUTTON, cntC); // Expand All
		buttonSetEnabled(COLLAPSE_ALL_BUTTON, cntE); // Collapse all
	}

	/**
	 * Make the move buttons enabled when a project is selected
	 */
	private void updateMoveButtons() {
		if (tree.getSelectionCount() == 1) {
			TreeItem ti = tree.getSelection()[0];
			// Is a project selected?
			if (ti.getParentItem() == null && ti.getChecked()) {
				int index = tree.indexOf(ti);
				buttonSetEnabled(MOVEUP_BUTTON, index > 0);
				buttonSetEnabled(MOVEDOWN_BUTTON, index < tree.getItemCount() - 1);
				return;
			}
		}
		buttonSetEnabled(MOVEUP_BUTTON, false);
		buttonSetEnabled(MOVEDOWN_BUTTON, false);
	}

}
