/*******************************************************************************
 * 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());
        }
    }
}
