/*******************************************************************************
 * Copyright (c) 2013, 2014 IBM 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:
 *     IBM Corporation - initial API and implementation
 *     Lars Vogel <Lars.Vogel@gmail.com> - Bug 440893
 *     Simon Scholz <simon.scholz@vogella.com> - Bug 436344
 *******************************************************************************/
package org.eclipse.e4.ui.bindings.tests;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.commands.Category;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.CommandManager;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.contexts.Context;
import org.eclipse.core.commands.contexts.ContextManager;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.ui.bindings.internal.BindingTable;
import org.eclipse.e4.ui.bindings.internal.BindingTableManager;
import org.eclipse.e4.ui.bindings.internal.ContextSet;
import org.eclipse.jface.bindings.Binding;
import org.eclipse.jface.bindings.keys.KeyBinding;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.junit.Before;
import org.junit.Test;

public class BindingTableTests {
	private static final String ID_DIALOG = "org.eclipse.ui.contexts.dialog";
	private static final String ID_DIALOG_AND_WINDOW = "org.eclipse.ui.contexts.dialogAndWindow";
	private static final String ID_WINDOW = "org.eclipse.ui.contexts.window";
	private static final String ID_TEXT = "org.eclipse.ui.textScope";
	private static final String ID_JAVA = "org.eclipse.jdt.ui.javaScope";
	private static final String ID_JS = "org.eclipse.wst.jsdt.ui.javaScriptScope";

	private static final String ABOUT_ID = "org.eclipse.ui.help.aboutAction";
	private static final String EXIT_ID = "org.eclipse.ui.file.exit";
	private static final String REFRESH_ID = "org.eclipse.ui.file.refresh";
	private static final String PASTE_ID = "org.eclipse.ui.edit.paste";
	private static final String CUT_ID = "org.eclipse.ui.edit.cut";
	private static final String COPY_ID = "org.eclipse.ui.edit.copy";
	private static final String TAG_ID = "org.eclipse.ui.text.wordCompletion";
	private static final String RENAME_ID = "org.eclipse.ui.file.rename";
	private static final String SHOW_TOOLTIP_ID = "org.eclipse.ui.text.showToolTip";
	private static final String CORR_INDENT_ID = "org.eclipse.jdt.ui.correctIndentation";
	private static final String INDENT_LINE_ID = "org.eclipse.wst.jsdt.ui.indentLine";

	static final String[] CONTEXTS = { ID_DIALOG_AND_WINDOW, "DAW", null,
			ID_DIALOG, "Dialog", ID_DIALOG_AND_WINDOW, ID_WINDOW, "Window",
			ID_DIALOG_AND_WINDOW, ID_TEXT, "Text Scope", ID_WINDOW, ID_JAVA,
			"Java scope", ID_TEXT, ID_JS, "JavaScript scope", ID_TEXT, };
	static final String[] ORDERED_IDS = { ID_DIALOG_AND_WINDOW, ID_DIALOG,
			ID_WINDOW, ID_TEXT, ID_JAVA, ID_JS, };

	static final String[] COMMANDS = { COPY_ID, "Copy", CUT_ID, "Cut",
			PASTE_ID, "Paste", REFRESH_ID, "Refresh", EXIT_ID, "Exit",
			ABOUT_ID, "About", TAG_ID, "Word Completion", RENAME_ID, "Rename",
			SHOW_TOOLTIP_ID, "Show Tooltip Description", CORR_INDENT_ID,
			"Correct Indentation", INDENT_LINE_ID, "Indent Line", };

	static final String[] BINDINGS = { COPY_ID, "M1+C", ID_WINDOW, CUT_ID,
			"M1+X", ID_WINDOW, PASTE_ID, "M1+V", ID_WINDOW, REFRESH_ID, "F5",
			ID_DIALOG_AND_WINDOW, EXIT_ID, "CTRL+Q", ID_DIALOG_AND_WINDOW,
			ABOUT_ID, "CTRL+5 A", ID_DIALOG_AND_WINDOW, TAG_ID, "ALT+/",
			ID_TEXT, RENAME_ID, "F2", ID_WINDOW, SHOW_TOOLTIP_ID, "F2",
			ID_TEXT, CORR_INDENT_ID, "CTRL+I", ID_JAVA, INDENT_LINE_ID,
			"CTRL+I", ID_JS, PASTE_ID, "SHIFT+Insert", ID_WINDOW, PASTE_ID,
			"CTRL+5 V", ID_TEXT, };

	static ArrayList<Binding> loadedBindings = null;
	static CommandManager commandManager = null;
	static ContextManager contextManager = null;
	static IEclipseContext workbenchContext;

	@Before
	public void setUp() throws Exception {
		if (loadedBindings == null) {
			IEclipseContext globalContext = Activator.getDefault().getGlobalContext();
			workbenchContext = globalContext.createChild("workbenchContext");
			loadedBindings = new ArrayList<>();
			contextManager = new ContextManager();
			ContextSet.setComparator(new ContextSet.CComp(contextManager));
			for (int i = 0; i < CONTEXTS.length; i += 3) {
				Context context = contextManager.getContext(CONTEXTS[i]);
				context.define(CONTEXTS[i + 1], null, CONTEXTS[i + 2]);
			}

			commandManager = new CommandManager();
			Category category = commandManager.getCategory("bogus");
			category.define("Bogus", null);
			for (int i = 0; i < COMMANDS.length; i += 2) {
				Command cmd = commandManager.getCommand(COMMANDS[i]);
				cmd.define(COMMANDS[i + 1], null, category);
			}
			for (int i = 0; i < BINDINGS.length; i += 3) {
				KeySequence seq = KeySequence.getInstance(BINDINGS[i + 1]);
				Command cmd = commandManager.getCommand(BINDINGS[i]);
				loadedBindings.add(new KeyBinding(seq,
						new ParameterizedCommand(cmd, null),
						"org.eclipse.ui.defaultAcceleratorConfiguration",
						BINDINGS[i + 2], null, null, null, Binding.SYSTEM));
			}
		}
	}

	@Test
	public void testOneTable() throws Exception {
		Binding about = getTestBinding(ABOUT_ID);
		KeySequence aboutSeq = KeySequence.getInstance("CTRL+5 A");
		KeySequence prefix = KeySequence.getInstance("CTRL+5");

		BindingTable table = loadTable(ID_DIALOG_AND_WINDOW);

		assertNotNull(about);
		Binding match = table.getPerfectMatch(aboutSeq);
		assertEquals(about, match);
		assertEquals(aboutSeq, table.getBestSequenceFor(
				about.getParameterizedCommand()).getTriggerSequence());

		Collection<Binding> sequences = table.getSequencesFor(about
				.getParameterizedCommand());
		assertEquals(1, sequences.size());
		assertEquals(aboutSeq, ((ArrayList<Binding>) sequences).get(0)
				.getTriggerSequence());

		Collection<Binding> partialMatches = table.getPartialMatches(prefix);
		assertEquals(1, partialMatches.size());
		assertEquals(about, ((ArrayList<Binding>) partialMatches).get(0));
	}

	@Test
	public void testTwoKeysOneCommand() throws Exception {
		BindingTable table = loadTable(ID_WINDOW);
		Binding paste = getTestBinding(PASTE_ID);
		ParameterizedCommand pasteCmd = paste.getParameterizedCommand();
		KeySequence ctrlV = KeySequence.getInstance("M1+V");
		KeySequence shiftIns = KeySequence.getInstance("Shift+Insert");
		Binding match1 = table.getPerfectMatch(ctrlV);
		assertEquals(pasteCmd, match1.getParameterizedCommand());
		Binding match2 = table.getPerfectMatch(shiftIns);
		assertFalse(match1 == match2);
		assertEquals(pasteCmd, match2.getParameterizedCommand());
	}

	@Test
	public void testLookupShortcut() {
		BindingTable table = loadTable(ID_WINDOW);
		Binding paste = getTestBinding(PASTE_ID);

		Binding match = table.getBestSequenceFor(paste
				.getParameterizedCommand());
		assertEquals(paste, match);
	}

	@Test
	public void testLookupShortcuts() throws Exception {
		BindingTable table = loadTable(ID_WINDOW);
		Binding paste = getTestBinding(PASTE_ID);

		Collection<Binding> sequences = table.getSequencesFor(paste
				.getParameterizedCommand());
		assertEquals(2, sequences.size());

		KeySequence second = KeySequence.getInstance("SHIFT+INSERT");
		Iterator<Binding> it = sequences.iterator();
		assertEquals(paste.getTriggerSequence(), it.next().getTriggerSequence());
		assertEquals(second, it.next().getTriggerSequence());
	}

	@Test
	public void testPartialMatch() throws Exception {
		BindingTable table = loadTable(ID_DIALOG_AND_WINDOW);
		KeySequence ctrl5 = KeySequence.getInstance("CTRL+5");
		KeySequence ctrl8 = KeySequence.getInstance("CTRL+8");
		assertTrue(table.isPartialMatch(ctrl5));
		assertFalse(table.isPartialMatch(ctrl8));
	}

	@Test
	public void testContextSet() {
		BindingTableManager manager = ContextInjectionFactory
				.make(BindingTableManager.class, workbenchContext);
		ArrayList<Context> window = new ArrayList<>();
		Context winContext = contextManager.getContext(ID_WINDOW);
		Context dawContext = contextManager.getContext(ID_DIALOG_AND_WINDOW);
		window.add(winContext);
		window.add(dawContext);
		ContextSet windowSet = manager.createContextSet(window);
		assertContextSet(windowSet, new String[] { ID_DIALOG_AND_WINDOW,
				ID_WINDOW });

		ArrayList<Context> text = new ArrayList<>(window);
		Context textContext = contextManager.getContext(ID_TEXT);
		text.add(textContext);
		ContextSet textSet = manager.createContextSet(text);
		assertContextSet(textSet, new String[] { ID_DIALOG_AND_WINDOW,
				ID_WINDOW, ID_TEXT });
	}

	@Test
	public void testContextSetSibling() {
		BindingTableManager manager = ContextInjectionFactory
				.make(BindingTableManager.class, workbenchContext);
		ArrayList<Context> all = new ArrayList<>();
		for (int i = 0; i < CONTEXTS.length; i += 3) {
			Context context = contextManager.getContext(CONTEXTS[i]);
			all.add(context);
		}
		ContextSet set = manager.createContextSet(all);
		assertContextSet(set, ORDERED_IDS);
	}

	@Test
	public void testSingleParentChainPerfectMatch() {
		BindingTableManager manager = ContextInjectionFactory
				.make(BindingTableManager.class, workbenchContext);

		manager.addTable(loadTable(ID_DIALOG_AND_WINDOW));
		manager.addTable(loadTable(ID_WINDOW));
		manager.addTable(loadTable(ID_TEXT));

		ArrayList<Context> window = new ArrayList<>();
		Context winContext = contextManager.getContext(ID_WINDOW);
		Context dawContext = contextManager.getContext(ID_DIALOG_AND_WINDOW);
		window.add(winContext);
		window.add(dawContext);
		ContextSet windowSet = manager.createContextSet(window);

		ArrayList<Context> text = new ArrayList<>(window);
		Context textContext = contextManager.getContext(ID_TEXT);
		text.add(textContext);
		ContextSet textSet = manager.createContextSet(text);

		Binding rename = getTestBinding(RENAME_ID);
		assertNotNull(rename);
		Binding showTooltip = getTestBinding(SHOW_TOOLTIP_ID);
		assertNotNull(showTooltip);

		Binding match = manager.getPerfectMatch(windowSet, rename
				.getTriggerSequence());
		assertEquals(rename, match);

		match = manager.getPerfectMatch(textSet, rename.getTriggerSequence());
		assertEquals(showTooltip, match);

		Binding about = getTestBinding(ABOUT_ID);
		match = manager.getPerfectMatch(textSet, about.getTriggerSequence());
		assertEquals(about, match);
	}

	@Test
	public void testSiblingsPerfectMatch() throws Exception {
		BindingTableManager manager = createManager();

		Binding correctIndent = getTestBinding(CORR_INDENT_ID);
		Binding indentLine = getTestBinding(INDENT_LINE_ID);

		assertEquals(correctIndent.getTriggerSequence(), indentLine
				.getTriggerSequence());
		ArrayList<Context> all = new ArrayList<>();
		for (int i = 0; i < CONTEXTS.length; i += 3) {
			Context context = contextManager.getContext(CONTEXTS[i]);
			all.add(context);
		}
		ContextSet set = manager.createContextSet(all);
		Binding match = manager.getPerfectMatch(set, correctIndent
				.getTriggerSequence());
		assertEquals(indentLine, match);
	}

	@Test
	public void testOneSiblingAtATimePerfectMatch() throws Exception {
		BindingTableManager manager = createManager();

		Binding correctIndent = getTestBinding(CORR_INDENT_ID);
		Binding indentLine = getTestBinding(INDENT_LINE_ID);
		assertEquals(correctIndent.getTriggerSequence(), indentLine
				.getTriggerSequence());

		ContextSet javaSet = createJavaSet(manager);

		ArrayList<Context> jsList = new ArrayList<>();
		for (int i = 0; i < CONTEXTS.length; i += 3) {
			Context context = contextManager.getContext(CONTEXTS[i]);
			if (!ID_JAVA.equals(context.getId())) {
				jsList.add(context);
			}
		}
		ContextSet jsSet = manager.createContextSet(jsList);

		Binding match = manager.getPerfectMatch(javaSet, correctIndent
				.getTriggerSequence());
		assertEquals(correctIndent, match);

		match = manager.getPerfectMatch(jsSet, correctIndent
				.getTriggerSequence());
		assertEquals(indentLine, match);
	}

	@Test
	public void testManagerLookupShortcut() throws Exception {
		BindingTableManager manager = createManager();
		Binding paste = getTestBinding(PASTE_ID);

		ArrayList<Context> window = new ArrayList<>();
		Context winContext = contextManager.getContext(ID_WINDOW);
		Context dawContext = contextManager.getContext(ID_DIALOG_AND_WINDOW);
		window.add(winContext);
		window.add(dawContext);
		ContextSet windowSet = manager.createContextSet(window);

		Binding match = manager.getBestSequenceFor(windowSet, paste
				.getParameterizedCommand());
		assertEquals(paste, match);
	}

	@Test
	public void testManagerLookupShortcutLongChain() throws Exception {
		BindingTableManager manager = createManager();
		Binding paste = getTestBinding(PASTE_ID);

		ContextSet javaSet = createJavaSet(manager);

		Binding match = manager.getBestSequenceFor(javaSet, paste
				.getParameterizedCommand());
		assertEquals(paste, match);
	}

	@Test
	public void testManagerLookupAllShortcuts() throws Exception {
		BindingTableManager manager = createManager();
		Binding paste = getTestBinding(PASTE_ID);

		ContextSet javaSet = createJavaSet(manager);

		Collection<Binding> sequences = manager.getSequencesFor(javaSet, paste
				.getParameterizedCommand());
		assertEquals(3, sequences.size());

		KeySequence second = KeySequence.getInstance("SHIFT+INSERT");
		KeySequence third = KeySequence.getInstance("CTRL+5 V");
		Iterator<Binding> it = sequences.iterator();
		assertEquals(paste.getTriggerSequence(), it.next().getTriggerSequence());
		assertEquals(second, it.next().getTriggerSequence());
		assertEquals(third, it.next().getTriggerSequence());
	}

	@Test
	public void testManagerPartialMatch() throws Exception {
		BindingTableManager manager = createManager();
		Binding about = getTestBinding(ABOUT_ID);
		Binding paste = getTestBinding(PASTE_ID);
		ContextSet javaSet = createJavaSet(manager);
		Binding pasteCtrl5 = manager.getPerfectMatch(javaSet, KeySequence
				.getInstance("CTRL+5 V"));
		assertEquals(paste.getParameterizedCommand(), pasteCtrl5
				.getParameterizedCommand());

		KeySequence ctrl5 = KeySequence.getInstance("CTRL+5");
		KeySequence ctrl8 = KeySequence.getInstance("CTRL+8");
		assertTrue(manager.isPartialMatch(javaSet, ctrl5));
		assertFalse(manager.isPartialMatch(javaSet, ctrl8));

		Collection<Binding> partialMatches = manager.getPartialMatches(javaSet,
				ctrl5);
		assertEquals(2, partialMatches.size());
		Iterator<Binding> it = partialMatches.iterator();
		assertEquals(pasteCtrl5, it.next());
		assertEquals(about, it.next());
	}

	private BindingTable loadTable(String contextId) {
		Context context = contextManager.getContext(contextId);
		BindingTable table = new BindingTable(context);
		for (Binding b : loadedBindings) {
			if (context.getId().equals(b.getContextId())) {
				table.addBinding(b);
			}
		}
		return table;
	}

	private Binding getTestBinding(String commandId) {
		for (Binding binding : loadedBindings) {
			if (commandId.equals(binding.getParameterizedCommand().getId())) {
				return binding;
			}
		}
		return null;
	}

	private void assertContextSet(ContextSet set, String[] contextIds) {
		List<Context> contexts = set.getContexts();
		assertEquals(contexts.toString(), contextIds.length, contexts.size());
		for (int i = 0; i < contextIds.length; i++) {
			assertEquals(contextIds[i], contexts.get(i).getId());
		}
	}

	private BindingTableManager createManager() throws Exception {
		BindingTableManager manager = ContextInjectionFactory
				.make(BindingTableManager.class, workbenchContext);

		for (int i = 0; i < CONTEXTS.length; i += 3) {
			manager.addTable(loadTable(CONTEXTS[i]));
		}
		return manager;
	}

	private ContextSet createJavaSet(BindingTableManager manager) {
		ArrayList<Context> javaList = new ArrayList<>();
		for (int i = 0; i < CONTEXTS.length; i += 3) {
			if (CONTEXTS[i].equals(ID_JS) || CONTEXTS[i].equals(ID_DIALOG)) {
				continue;
			}
			Context context = contextManager.getContext(CONTEXTS[i]);
			javaList.add(context);
		}
		return manager.createContextSet(javaList);
	}
}
