/*
 * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.awt.event;

import java.awt.Component;
import java.awt.Event;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.io.Serial;
import java.util.Arrays;

import sun.awt.AWTAccessor;
import sun.util.logging.PlatformLogger;

/**
 * The root event class for all component-level input events.
 *
 * Input events are delivered to listeners before they are
 * processed normally by the source where they originated.
 * This allows listeners and component subclasses to "consume"
 * the event so that the source will not process them in their
 * default manner.  For example, consuming mousePressed events
 * on a Button component will prevent the Button from being
 * activated.
 *
 * @author Carl Quinn
 *
 * @see KeyEvent
 * @see KeyAdapter
 * @see MouseEvent
 * @see MouseAdapter
 * @see MouseMotionAdapter
 *
 * @since 1.1
 * @sealedGraph
 */
public abstract sealed class InputEvent extends ComponentEvent
    permits KeyEvent,
            MouseEvent {

    private static final PlatformLogger logger = PlatformLogger.getLogger("java.awt.event.InputEvent");

    /**
     * The Shift key modifier constant.
     *
     * @deprecated It is recommended that SHIFT_DOWN_MASK and
     *             {@link #getModifiersEx()} be used instead
     */
    @Deprecated(since = "9")
    public static final int SHIFT_MASK = Event.SHIFT_MASK;

    /**
     * The Control key modifier constant.
     *
     * @deprecated It is recommended that CTRL_DOWN_MASK and
     *             {@link #getModifiersEx()} be used instead
     */
    @Deprecated(since = "9")
    public static final int CTRL_MASK = Event.CTRL_MASK;

    /**
     * The Meta key modifier constant.
     *
     * @deprecated It is recommended that META_DOWN_MASK and
     *             {@link #getModifiersEx()} be used instead
     */
    @Deprecated(since = "9")
    public static final int META_MASK = Event.META_MASK;

    /**
     * The Alt key modifier constant.
     *
     * @deprecated It is recommended that ALT_DOWN_MASK and
     *             {@link #getModifiersEx()} be used instead
     */
    @Deprecated(since = "9")
    public static final int ALT_MASK = Event.ALT_MASK;

    /**
     * The AltGraph key modifier constant.
     *
     * @deprecated It is recommended that ALT_GRAPH_DOWN_MASK and
     *             {@link #getModifiersEx()} be used instead
     */
    @Deprecated(since = "9")
    public static final int ALT_GRAPH_MASK = 1 << 5;

    /**
     * The Mouse Button1 modifier constant.
     *
     * @deprecated It is recommended that BUTTON1_DOWN_MASK and
     *             {@link #getModifiersEx()} be used instead
     */
    @Deprecated(since = "9")
    public static final int BUTTON1_MASK = 1 << 4;

    /**
     * The Mouse Button2 modifier constant.
     *
     * @deprecated It is recommended that BUTTON2_DOWN_MASK and
     *             {@link #getModifiersEx()} be used instead. Note that
     *             BUTTON2_MASK has the same value as ALT_MASK.
     */
    @Deprecated(since = "9")
    public static final int BUTTON2_MASK = Event.ALT_MASK;

    /**
     * The Mouse Button3 modifier constant.
     *
     * @deprecated It is recommended that BUTTON3_DOWN_MASK and
     *             {@link #getModifiersEx()} be used instead. Note that
     *             BUTTON3_MASK has the same value as META_MASK.
     */
    @Deprecated(since = "9")
    public static final int BUTTON3_MASK = Event.META_MASK;

    /**
     * The Shift key extended modifier constant.
     * @since 1.4
     */
    public static final int SHIFT_DOWN_MASK = 1 << 6;

    /**
     * The Control key extended modifier constant.
     * @since 1.4
     */
    public static final int CTRL_DOWN_MASK = 1 << 7;

    /**
     * The Meta key extended modifier constant.
     * @since 1.4
     */
    public static final int META_DOWN_MASK = 1 << 8;

    /**
     * The Alt key extended modifier constant.
     * @since 1.4
     */
    public static final int ALT_DOWN_MASK = 1 << 9;

    /**
     * The Mouse Button1 extended modifier constant.
     * @since 1.4
     */
    public static final int BUTTON1_DOWN_MASK = 1 << 10;

    /**
     * The Mouse Button2 extended modifier constant.
     * @since 1.4
     */
    public static final int BUTTON2_DOWN_MASK = 1 << 11;

    /**
     * The Mouse Button3 extended modifier constant.
     * @since 1.4
     */
    public static final int BUTTON3_DOWN_MASK = 1 << 12;

    /**
     * The AltGraph key extended modifier constant.
     * @since 1.4
     */
    public static final int ALT_GRAPH_DOWN_MASK = 1 << 13;

    /**
     * An array of extended modifiers for additional buttons.
     * @see #getButtonDownMasks()
     * There are twenty buttons fit into 4byte space.
     * one more bit is reserved for FIRST_HIGH_BIT.
     * @since 1.7
     */
    private static final int [] BUTTON_DOWN_MASK = new int [] { BUTTON1_DOWN_MASK,
                                                               BUTTON2_DOWN_MASK,
                                                               BUTTON3_DOWN_MASK,
                                                               1<<14, //4th physical button (this is not a wheel!)
                                                               1<<15, //(this is not a wheel!)
                                                               1<<16,
                                                               1<<17,
                                                               1<<18,
                                                               1<<19,
                                                               1<<20,
                                                               1<<21,
                                                               1<<22,
                                                               1<<23,
                                                               1<<24,
                                                               1<<25,
                                                               1<<26,
                                                               1<<27,
                                                               1<<28,
                                                               1<<29,
                                                               1<<30};

    /**
     * A method to access an array of extended modifiers for additional buttons.
     * @since 1.7
     */
    private static int [] getButtonDownMasks(){
        return Arrays.copyOf(BUTTON_DOWN_MASK, BUTTON_DOWN_MASK.length);
    }


    /**
     * A method to obtain a mask for any existing mouse button.
     * The returned mask may be used for different purposes. Following are some of them:
     * <ul>
     * <li> {@link java.awt.Robot#mousePress(int) mousePress(buttons)} and
     *      {@link java.awt.Robot#mouseRelease(int) mouseRelease(buttons)}
     * <li> as a {@code modifiers} parameter when creating a new {@link MouseEvent} instance
     * <li> to check {@link MouseEvent#getModifiersEx() modifiersEx} of existing {@code MouseEvent}
     * </ul>
     * @param button is a number to represent a button starting from 1.
     * For example,
     * <pre>
     * int button = InputEvent.getMaskForButton(1);
     * </pre>
     * will have the same meaning as
     * <pre>
     * int button = InputEvent.getMaskForButton(MouseEvent.BUTTON1);
     * </pre>
     * because {@link MouseEvent#BUTTON1 MouseEvent.BUTTON1} equals to 1.
     * If a mouse has three enabled buttons(see {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()})
     * then the values from the left column passed into the method will return
     * corresponding values from the right column:
     * <PRE>
     *    <b>button </b>   <b>returned mask</b>
     *    {@link MouseEvent#BUTTON1 BUTTON1}  {@link MouseEvent#BUTTON1_DOWN_MASK BUTTON1_DOWN_MASK}
     *    {@link MouseEvent#BUTTON2 BUTTON2}  {@link MouseEvent#BUTTON2_DOWN_MASK BUTTON2_DOWN_MASK}
     *    {@link MouseEvent#BUTTON3 BUTTON3}  {@link MouseEvent#BUTTON3_DOWN_MASK BUTTON3_DOWN_MASK}
     * </PRE>
     * If a mouse has more than three enabled buttons then more values
     * are admissible (4, 5, etc.). There is no assigned constants for these extended buttons.
     * The button masks for the extra buttons returned by this method have no assigned names like the
     * first three button masks.
     * <p>
     * This method has the following implementation restriction.
     * It returns masks for a limited number of buttons only. The maximum number is
     * implementation dependent and may vary.
     * This limit is defined by the relevant number
     * of buttons that may hypothetically exist on the mouse but it is greater than the
     * {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
     *
     * @return a mask for an existing mouse button.
     * @throws IllegalArgumentException if {@code button} is less than zero or greater than the number
     *         of button masks reserved for buttons
     * @since 1.7
     * @see java.awt.MouseInfo#getNumberOfButtons()
     * @see Toolkit#areExtraMouseButtonsEnabled()
     * @see MouseEvent#getModifiers()
     * @see MouseEvent#getModifiersEx()
     */
    public static int getMaskForButton(int button) {
        if (button <= 0 || button > BUTTON_DOWN_MASK.length) {
            throw new IllegalArgumentException("button doesn't exist " + button);
        }
        return BUTTON_DOWN_MASK[button - 1];
    }

    // the constant below MUST be updated if any extra modifier
    // bits are to be added!
    // in fact, it is undesirable to add modifier bits
    // to the same field as this may break applications
    // see bug# 5066958
    static final int FIRST_HIGH_BIT = 1 << 31;

    static final int JDK_1_3_MODIFIERS = SHIFT_DOWN_MASK - 1;
    static final int HIGH_MODIFIERS = ~( FIRST_HIGH_BIT - 1 );

    /**
     * The input event's Time stamp in UTC format.  The time stamp
     * indicates when the input event was created.
     *
     * @serial
     * @see #getWhen()
     */
    long when;

    /**
     * The state of the modifier mask at the time the input
     * event was fired.
     *
     * @serial
     * @see #getModifiers()
     * @see #getModifiersEx()
     * @see java.awt.event.KeyEvent
     * @see java.awt.event.MouseEvent
     */
    int modifiers;

    /*
     * A flag that indicates that this instance can be used to access
     * the system clipboard.
     * This should be false in a headless environment, true in a headful one.
     */
    private transient boolean canAccessSystemClipboard;

    static {
        /* ensure that the necessary native libraries are loaded */
        NativeLibLoader.loadLibraries();
        if (!GraphicsEnvironment.isHeadless()) {
            initIDs();
        }
        AWTAccessor.setInputEventAccessor(
            new AWTAccessor.InputEventAccessor() {
                public int[] getButtonDownMasks() {
                    return InputEvent.getButtonDownMasks();
                }

                public boolean canAccessSystemClipboard(InputEvent event) {
                    return event.canAccessSystemClipboard;
                }

                @Override
                public void setCanAccessSystemClipboard(InputEvent event,
                        boolean canAccessSystemClipboard) {
                    event.canAccessSystemClipboard = canAccessSystemClipboard;
                }
            });
    }

    /**
     * Initialize JNI field and method IDs for fields that may be
       accessed from C.
     */
    private static native void initIDs();

    /**
     * Constructs an InputEvent object with the specified source component,
     * modifiers, and type.
     * <p> This method throws an
     * {@code IllegalArgumentException} if {@code source}
     * is {@code null}.
     *
     * @param source the object where the event originated
     * @param id           the integer that identifies the event type.
     *                     It is allowed to pass as parameter any value that
     *                     allowed for some subclass of {@code InputEvent} class.
     *                     Passing in the value different from those values result
     *                     in unspecified behavior
     * @param when         a long int that gives the time the event occurred.
     *                     Passing negative or zero value
     *                     is not recommended
     * @param modifiers    a modifier mask describing the modifier keys and mouse
     *                     buttons (for example, shift, ctrl, alt, and meta) that
     *                     are down during the event.
     *                     Only extended modifiers are allowed to be used as a
     *                     value for this parameter (see the {@link InputEvent#getModifiersEx}
     *                     class for the description of extended modifiers).
     *                     Passing negative parameter
     *                     is not recommended.
     *                     Zero value means that no modifiers were passed
     * @throws IllegalArgumentException if {@code source} is null
     * @see #getSource()
     * @see #getID()
     * @see #getWhen()
     * @see #getModifiers()
     */
    InputEvent(Component source, int id, long when, int modifiers) {
        super(source, id);
        this.when = when;
        this.modifiers = modifiers;
        canAccessSystemClipboard = canAccessSystemClipboard();
    }

    private boolean canAccessSystemClipboard() {
        return !GraphicsEnvironment.isHeadless();
    }

    /**
     * Returns whether or not the Shift modifier is down on this event.
     * @return whether or not the Shift modifier is down on this event
     */
    public boolean isShiftDown() {
        return (modifiers & SHIFT_DOWN_MASK) != 0;
    }

    /**
     * Returns whether or not the Control modifier is down on this event.
     * @return whether or not the Control modifier is down on this event
     */
    public boolean isControlDown() {
        return (modifiers & CTRL_DOWN_MASK) != 0;
    }

    /**
     * Returns whether or not the Meta modifier is down on this event.
     * @return whether or not the Meta modifier is down on this event
     */
    public boolean isMetaDown() {
        return (modifiers & META_DOWN_MASK) != 0;
    }

    /**
     * Returns whether or not the Alt modifier is down on this event.
     * @return whether or not the Alt modifier is down on this event
     */
    public boolean isAltDown() {
        return (modifiers & ALT_DOWN_MASK) != 0;
    }

    /**
     * Returns whether or not the AltGraph modifier is down on this event.
     * @return whether or not the AltGraph modifier is down on this event
     */
    public boolean isAltGraphDown() {
        return (modifiers & ALT_GRAPH_DOWN_MASK) != 0;
    }

    /**
     * Returns the difference in milliseconds between the timestamp of when this event occurred and
     * midnight, January 1, 1970 UTC.
     * @return the difference in milliseconds between the timestamp and midnight, January 1, 1970 UTC
     */
    public long getWhen() {
        return when;
    }

    /**
     * Returns the modifier mask for this event.
     *
     * @return the modifier mask for this event
     * @deprecated It is recommended that extended modifier keys and
     *             {@link #getModifiersEx()} be used instead
     */
    @Deprecated(since = "9")
    public int getModifiers() {
        return modifiers & (JDK_1_3_MODIFIERS | HIGH_MODIFIERS);
    }

    /**
     * Returns the extended modifier mask for this event.
     * <P>
     * Extended modifiers are the modifiers that ends with the _DOWN_MASK suffix,
     * such as ALT_DOWN_MASK, BUTTON1_DOWN_MASK, and others.
     * <P>
     * Extended modifiers represent the state of all modal keys,
     * such as ALT, CTRL, META, and the mouse buttons just after
     * the event occurred.
     * <P>
     * For example, if the user presses <b>button 1</b> followed by
     * <b>button 2</b>, and then releases them in the same order,
     * the following sequence of events is generated:
     * <PRE>
     *    {@code MOUSE_PRESSED}:  {@code BUTTON1_DOWN_MASK}
     *    {@code MOUSE_PRESSED}:  {@code BUTTON1_DOWN_MASK | BUTTON2_DOWN_MASK}
     *    {@code MOUSE_RELEASED}: {@code BUTTON2_DOWN_MASK}
     *    {@code MOUSE_CLICKED}:  {@code BUTTON2_DOWN_MASK}
     *    {@code MOUSE_RELEASED}:
     *    {@code MOUSE_CLICKED}:
     * </PRE>
     * <P>
     * It is not recommended to compare the return value of this method
     * using {@code ==} because new modifiers can be added in the future.
     * For example, the appropriate way to check that SHIFT and BUTTON1 are
     * down, but CTRL is up is demonstrated by the following code:
     * <PRE>
     *    int onmask = SHIFT_DOWN_MASK | BUTTON1_DOWN_MASK;
     *    int offmask = CTRL_DOWN_MASK;
     *    if ((event.getModifiersEx() &amp; (onmask | offmask)) == onmask) {
     *        ...
     *    }
     * </PRE>
     * The above code will work even if new modifiers are added.
     *
     * @return the extended modifier mask for this event
     * @since 1.4
     */
    public int getModifiersEx() {
        return modifiers & ~JDK_1_3_MODIFIERS;
    }

    /**
     * Consumes this event so that it will not be processed
     * in the default manner by the source which originated it.
     */
    public void consume() {
        consumed = true;
    }

    /**
     * Returns whether or not this event has been consumed.
     * @return whether or not this event has been consumed
     * @see #consume
     */
    public boolean isConsumed() {
        return consumed;
    }

    /**
     * Use serialVersionUID from JDK 1.1 for interoperability.
     */
    @Serial
    private static final long serialVersionUID = -2482525981698309786L;

    /**
     * Returns a String describing the extended modifier keys and
     * mouse buttons, such as "Shift", "Button1", or "Ctrl+Shift".
     * These strings can be localized by changing the
     * {@code awt.properties} file.
     * <p>
     * Note that passing negative parameter is incorrect,
     * and will cause the returning an unspecified string.
     * Zero parameter means that no modifiers were passed and will
     * cause the returning an empty string.
     *
     * @param modifiers a modifier mask describing the extended
     *                modifier keys and mouse buttons for the event
     * @return a text description of the combination of extended
     *         modifier keys and mouse buttons that were held down
     *         during the event.
     * @since 1.4
     */
    public static String getModifiersExText(int modifiers) {
        StringBuilder buf = new StringBuilder();
        if ((modifiers & InputEvent.META_DOWN_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.meta", "Meta"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.control", "Ctrl"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.alt", "Alt"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.shift", "Shift"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.altGraph", "Alt Graph"));
            buf.append("+");
        }

        int buttonNumber = 1;
        for (int mask : InputEvent.BUTTON_DOWN_MASK){
            if ((modifiers & mask) != 0) {
                buf.append(Toolkit.getProperty("AWT.button"+buttonNumber, "Button"+buttonNumber));
                buf.append("+");
            }
            buttonNumber++;
        }
        if (buf.length() > 0) {
            buf.setLength(buf.length()-1); // remove trailing '+'
        }
        return buf.toString();
    }
}
