/*
 * Copyright 2015-2024 MicroEJ Corp. All rights reserved.
 * This library is provided in source code for use, modification and test, subject to license terms.
 * Any modification of the source code will break MicroEJ Corp. warranties on the whole library.
 */
package ej.mwt.event;

import ej.annotation.Nullable;
import ej.microui.event.Event;
import ej.microui.event.generator.Buttons;
import ej.microui.event.generator.Pointer;
import ej.mwt.Desktop;
import ej.mwt.Widget;

/**
 * Dispatches the pointer events received on a desktop to its children.
 * <p>
 * Pointer events are grouped in sessions. A session starts when the pointer is pressed, and ends when the pointer is
 * released.
 * <p>
 * Each event of the session is sent to the leaf widget that was under the pointer during the press event (see
 * {@link Desktop#getWidgetAt(int, int)}), then sent to all its parent hierarchy. Events are sent only to enabled
 * widgets (see {@link Widget#isEnabled()}).
 * <p>
 * Once a widget has consumed an event, it will be the only one to receive the next events during the session. All the
 * other widgets (ancestors or offsprings) receive an {@link #EXITED} event.
 * <p>
 * When the pointer exits the bounds of a widget in the hierarchy, this widget receives an {@link #EXITED} event and
 * won't receive any event from the session except if it has consumed an event.
 */
public class PointerEventDispatcher extends EventDispatcher {

    /**
     * The "exited" action.
     */
    public static final int EXITED = 0x00;

    /**
     * Creates a desktop event dispatcher.
     *
     * @param desktop
     *            the desktop to dispatch in.
     */
    public PointerEventDispatcher(Desktop desktop) {
        super(desktop);
    }

    @Override
    public void initialize() {
        super.initialize();
        DesktopEventGenerator eventGenerator = createEventGenerator();
        eventGenerator.addToSystemPool();
        this.exitEvent = eventGenerator.buildEvent(EXITED);
        this.eventGenerator = eventGenerator;
    }

    @Override
    public void dispose() {
        super.dispose();
        DesktopEventGenerator eventGenerator = this.eventGenerator;
        if (eventGenerator != null) {
            eventGenerator.removeFromSystemPool();
            this.eventGenerator = null;
            this.exitEvent = 0;
        }
        this.pressedHierarchyLeaf = null;
        this.consumerWidget = null;
    }

    /**
     * Returns the leaf widget of the hierarchy which is subscribed to the ongoing pointer session.
     * <p>
     * When a pointer session is started, all the enabled widgets under the pointer become subscribed to this session.
     * <p>
     * The reference to this widget is initialized when the pointer is pressed ; it is updated when the pointer is
     * dragged outside of the bounds of the widget or when a pointer event is consumed ; and it is reset when the
     * pointer is released or when the pointer is dragged outside of the bounds of each subscribed widgets.
     *
     * @return the leaf widget of the subscribed hierarchy, or <code>null</code> if there is none.
     */
    @Nullable
    public Widget getPressedHierarchyLeaf() {
        return this.pressedHierarchyLeaf;
    }

    /**
     * Returns the widget which has consumed the ongoing pointer session.
     * <p>
     * When a pointer event is been consumed during a pointer session, it becomes the only widget to be subscribed to
     * this session.
     * <p>
     * The reference to this widget is initialized when a pointer event is consumed ; and it is reset when the pointer
     * is released or when the pointer is dragged outside of the bounds of the widget.
     *
     * @return the consumer widget, or <code>null</code> if there is none.
     */
    @Nullable
    public Widget getConsumerWidget() {
        return this.consumerWidget;
    }

    /**
     * Creates the event generator which is responsible for generating exit events to the widgets. By default, a
     * {@link DesktopEventGenerator desktop event generator} is created.
     *
     * @return the event generator.
     * @see DesktopEventGenerator
     */
    protected DesktopEventGenerator createEventGenerator() {
        return new DesktopEventGenerator();
    }

    /**
     * Gets the event generator of this dispatcher.
     *
     * @return the event generator of this dispatcher.
     */
    @Nullable
    protected DesktopEventGenerator getEventGenerator() {
        return this.eventGenerator;
    }

    @Override
    public boolean dispatchEvent(int event) {
        // dispatch events (separate pointer and other events)
        int type = Event.getType(event);
        if (type == Pointer.EVENT_TYPE) {
            pointerEvent(event);
            return true;
        }
        return false;
    }

    /**
     * Returns whether or not the given event is an exited event.
     *
     * @param event
     *            the event to check.
     * @return true if the given event is an exited event, false otherwise.
     */
    public static boolean isExited(int event) {
        return (Event.getType(event) == DesktopEventGenerator.EVENT_TYPE && DesktopEventGenerator.getAction(event) == EXITED);
    }
}
