/*******************************************************************************
 * Copyright (c) 2000, 2015 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
 *******************************************************************************/

package org.eclipse.ui.internal.keys;

import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.StatusLineContributionItem;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.internal.WorkbenchWindow;

/**
 * <p>
 * The mutable state of the key binding architecture. This is the only piece of
 * the key binding architecture that changes (internally). It keeps track of
 * what partial key strokes the user has entered. In the case of functional
 * groups of key bindings, it allows the user to keep part of the key sequence
 * even after a match has been made. Only after releasing all of the modifier
 * keys would the sequence reset itself.
 * </p>
 * <p>
 * In the current implementation, a partial reset results in only one key stroke
 * being left in the sequence. However, this may change in the future.
 * </p>
 *
 * @since 3.0
 */
class KeyBindingState {

	/**
	 * The workbench window associated with this state. The state can only exist for
	 * one window. When the focus leaves this window then the mode must
	 * automatically be reset.
	 */
	private IWorkbenchWindow associatedWindow;

	/**
	 * This is the current extent of the sequence entered by the user. In an
	 * application with only single-stroke key bindings, this will also be empty.
	 * However, in applications with multi-stroke key bindings, this is the sequence
	 * entered by the user that partially matches another one of the application's
	 * active key bindings.
	 */
	private KeySequence currentSequence;

	/**
	 * The workbench that should be notified of changes to the key binding state.
	 * This is done by updating one of the contribution items on the status line.
	 */
	private final IWorkbench workbench;

	/**
	 * Constructs a new instance of <code>KeyBindingState</code> with an empty key
	 * sequence, set to reset fully.
	 *
	 * @param workbenchToNotify The workbench that this state should keep advised of
	 *                          changes to the key binding state; must not be
	 *                          <code>null</code>.
	 */
	KeyBindingState(IWorkbench workbenchToNotify) {
		currentSequence = KeySequence.getInstance();
		workbench = workbenchToNotify;
		associatedWindow = workbench.getActiveWorkbenchWindow();
	}

	/**
	 * An accessor for the workbench window associated with this state. This should
	 * never be <code>null</code>, as the setting follows the last workbench window
	 * to have focus.
	 *
	 * @return The workbench window to which the key binding architecture is
	 *         currently attached; should never be <code>null</code>.
	 */
	IWorkbenchWindow getAssociatedWindow() {
		return associatedWindow;
	}

	/**
	 * An accessor for the current key sequence waiting for completion.
	 *
	 * @return The current incomplete key sequence; never <code>null</code>, but may
	 *         be empty.
	 */
	KeySequence getCurrentSequence() {
		return currentSequence;
	}

	/**
	 * Gets the status line contribution item which the key binding architecture
	 * uses to keep the user up-to-date as to the current state.
	 *
	 * @return The status line contribution item, if any; <code>null</code>, if
	 *         none.
	 */
	StatusLineContributionItem getStatusLine() {
		if (associatedWindow instanceof WorkbenchWindow) {
			WorkbenchWindow window = (WorkbenchWindow) associatedWindow;
			IStatusLineManager statusLine = window.getStatusLineManager();
			// TODO implicit dependency on IDE's action builder
			// @issue implicit dependency on IDE's action builder
			if (statusLine != null) { // this can be null if we're exiting
				IContributionItem item = statusLine.find("ModeContributionItem"); //$NON-NLS-1$
				if (item instanceof StatusLineContributionItem) {
					return ((StatusLineContributionItem) item);
				}
			}
		}

		return null;
	}

	/**
	 * <p>
	 * Resets the state based on the current properties. If the state is to collapse
	 * fully or if there are no key strokes, then it sets the state to have an empty
	 * key sequence. Otherwise, it leaves the first key stroke in the sequence.
	 * </p>
	 * <p>
	 * The workbench's status lines are updated, if appropriate.
	 * </p>
	 */
	void reset() {
		currentSequence = KeySequence.getInstance();
		updateStatusLines();
	}

	/**
	 * A mutator for the workbench window to which this state is associated.
	 *
	 * @param window The workbench window to associated; should never be
	 *               <code>null</code>.
	 */
	void setAssociatedWindow(IWorkbenchWindow window) {
		associatedWindow = window;
	}

	/**
	 * A mutator for the partial sequence entered by the user.
	 *
	 * @param sequence The current key sequence; should not be <code>null</code>,
	 *                 but may be empty.
	 */
	void setCurrentSequence(KeySequence sequence) {
		currentSequence = sequence;
		updateStatusLines();
	}

	/**
	 * Updates the text of the status line of the associated shell with the current
	 * sequence.
	 */
	private void updateStatusLines() {
		StatusLineContributionItem statusLine = getStatusLine();
		if (statusLine != null) {
			statusLine.setText(getCurrentSequence().format());
		}
	}
}
