/* ========================================================================= */
/**
 * @file surface.c
 *
 * @copyright
 * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
 * Copyright 2023 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "surface.h"

#include <libbase/libbase.h>
#include <stdlib.h>
#include <wayland-server-protocol.h>
#include <wayland-util.h>
#define WLR_USE_UNSTABLE
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_xdg_shell.h>
#undef WLR_USE_UNSTABLE

#include "element.h"
#include "container.h"
#include "gfxbuf.h"  // IWYU pragma: keep
#include "input.h"
#include "util.h"
#include "test.h"  // IWYU pragma: keep

/* == Declarations ========================================================= */

/** State of a `struct wlr_surface`, encapsuled for toolkit. */
struct _wlmtk_surface_t {
    /** Super class of the surface: An element. */
    wlmtk_element_t           super_element;
    /** Virtual method table of the super element before extending it. */
    wlmtk_element_vmt_t       orig_super_element_vmt;
    /** Seat. */
    struct wlr_seat           *wlr_seat_ptr;

    /** The `struct wlr_surface` wrapped. */
    struct wlr_surface        *wlr_surface_ptr;
    /** The `struct wlr_xdg_surface` wrapped. */
    struct wlr_xdg_surface        *wlr_xdg_surface_ptr;

    /** The scene API node displaying a surface and all it's sub-surfaces. */
    struct wlr_scene_tree     *wlr_scene_tree_ptr;
    /** Listener for the `destroy` signal of `wlr_scene_tree_ptr->node`. */
    struct wl_listener        wlr_scene_tree_node_destroy_listener;

    /** Committed width of the surface, in pixels. */
    int                       committed_width;
    /** Committed height of the surface, in pixels. */
    int                       committed_height;

    /** Listener for the `events.commit` signal of `wlr_surface`. */
    struct wl_listener        surface_commit_listener;
    /** Listener for the `map` signal of `wlr_surface`. */
    struct wl_listener        surface_map_listener;
    /** Listener for the `map` signal of `wlr_surface`. */
    struct wl_listener        surface_unmap_listener;

    /** Listener for @ref wlmtk_element_events_t::pointer_leave. */
    struct wl_listener        element_pointer_leave_listener;
    /** Listener for @ref wlmtk_element_events_t::pointer_motion. */
    struct wl_listener        element_pointer_motion_listener;

    /** Whether this surface is activated, ie. has keyboard focus. */
    bool                      activated;
};

static bool _wlmtk_surface_init(
    wlmtk_surface_t *surface_ptr,
    struct wlr_surface *wlr_surface_ptr);
static bool _wlmtk_xdg_surface_init(
    wlmtk_surface_t *surface_ptr,
    struct wlr_xdg_surface *wlr_xdg_surface_ptr);
static void _wlmtk_surface_fini(wlmtk_surface_t *surface_ptr);

static void _wlmtk_surface_element_destroy(wlmtk_element_t *element_ptr);
static struct wlr_scene_node *_wlmtk_surface_element_create_scene_node(
    wlmtk_element_t *element_ptr,
    struct wlr_scene_tree *wlr_scene_tree_ptr);
static void _wlmtk_surface_element_get_dimensions(
    wlmtk_element_t *element_ptr,
    int *left_ptr,
    int *top_ptr,
    int *right_ptr,
    int *bottom_ptr);
static bool _wlmtk_surface_element_pointer_accepts_motion(
    wlmtk_element_t *element_ptr,
    wlmtk_pointer_motion_event_t *motion_event_ptr);
static bool _wlmtk_surface_element_pointer_button(
    wlmtk_element_t *element_ptr,
    const wlmtk_button_event_t *button_event_ptr);
static bool _wlmtk_surface_element_pointer_axis(
    wlmtk_element_t *element_ptr,
    struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr);
static bool _wlmtk_surface_element_keyboard_event(
    wlmtk_element_t *element_ptr,
    struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr);

static void _wlmtk_surface_handle_wlr_scene_tree_node_destroy(
    struct wl_listener *listener_ptr,
    void *data_ptr);
static void _wlmtk_surface_handle_surface_commit(
    struct wl_listener *listener_ptr,
    void *data_ptr);
static void _wlmtk_surface_handle_surface_map(
    struct wl_listener *listener_ptr,
    void *data_ptr);
static void _wlmtk_surface_handle_surface_unmap(
    struct wl_listener *listener_ptr,
    void *data_ptr);
static void _wlmtk_surface_handle_element_pointer_leave(
    struct wl_listener *listener_ptr,
    void *data_ptr);
static void _wlmtk_surface_handle_element_pointer_motion(
    struct wl_listener *listener_ptr,
    void *data_ptr);

static void _wlmtk_surface_commit_size(
    wlmtk_surface_t *surface_ptr,
    int width,
    int height);

/* == Data ================================================================= */

/** Method table for the element's virtual methods. */
static const wlmtk_element_vmt_t surface_element_vmt = {
    .destroy = _wlmtk_surface_element_destroy,
    .create_scene_node = _wlmtk_surface_element_create_scene_node,
    .get_dimensions = _wlmtk_surface_element_get_dimensions,
    .pointer_accepts_motion = _wlmtk_surface_element_pointer_accepts_motion,
    .pointer_button = _wlmtk_surface_element_pointer_button,
    .pointer_axis = _wlmtk_surface_element_pointer_axis,
    .keyboard_event = _wlmtk_surface_element_keyboard_event,
};

/* == Exported methods ===================================================== */

/* ------------------------------------------------------------------------- */
wlmtk_surface_t *wlmtk_surface_create(
    struct wlr_surface *wlr_surface_ptr,
    struct wlr_seat *wlr_seat_ptr)
{
    wlmtk_surface_t *surface_ptr = logged_calloc(1, sizeof(wlmtk_surface_t));
    if (NULL == surface_ptr) return NULL;

    if (!_wlmtk_surface_init(surface_ptr, wlr_surface_ptr)) {
        wlmtk_surface_destroy(surface_ptr);
        return NULL;
    }
    surface_ptr->wlr_seat_ptr = wlr_seat_ptr;

    return surface_ptr;
}

/* ------------------------------------------------------------------------- */
wlmtk_surface_t *wlmtk_xdg_surface_create(
    struct wlr_xdg_surface *wlr_xdg_surface_ptr,
    struct wlr_seat *wlr_seat_ptr)
{
    wlmtk_surface_t *surface_ptr = logged_calloc(1, sizeof(wlmtk_surface_t));
    if (NULL == surface_ptr) return NULL;

    if (!_wlmtk_xdg_surface_init(surface_ptr, wlr_xdg_surface_ptr)) {
        wlmtk_surface_destroy(surface_ptr);
        return NULL;
    }
    surface_ptr->wlr_seat_ptr = wlr_seat_ptr;

    return surface_ptr;
}

/* ------------------------------------------------------------------------- */
void wlmtk_surface_destroy(wlmtk_surface_t *surface_ptr)
{
    _wlmtk_surface_fini(surface_ptr);
    free(surface_ptr);
}

/* ------------------------------------------------------------------------- */
wlmtk_element_t *wlmtk_surface_element(wlmtk_surface_t *surface_ptr)
{
    return &surface_ptr->super_element;
}

/* ------------------------------------------------------------------------- */
void wlmtk_surface_get_size(
    wlmtk_surface_t *surface_ptr,
    int *width_ptr,
    int *height_ptr)
{
    if (NULL != width_ptr) *width_ptr = surface_ptr->committed_width;
    if (NULL != height_ptr) *height_ptr = surface_ptr->committed_height;
}

/* ------------------------------------------------------------------------- */
void wlmtk_surface_set_activated(
    wlmtk_surface_t *surface_ptr,
    bool activated)
{
    if (surface_ptr->activated == activated) return;

    if (NULL != surface_ptr->wlr_seat_ptr) {
        struct wlr_keyboard *wlr_keyboard_ptr = wlr_seat_get_keyboard(
            surface_ptr->wlr_seat_ptr);
        if (activated) {
            if (NULL != wlr_keyboard_ptr) {
                wlr_seat_keyboard_notify_enter(
                    surface_ptr->wlr_seat_ptr,
                    surface_ptr->wlr_surface_ptr,
                    wlr_keyboard_ptr->keycodes,
                    wlr_keyboard_ptr->num_keycodes,
                    &wlr_keyboard_ptr->modifiers);
            }
        } else if (surface_ptr->wlr_seat_ptr->keyboard_state.focused_surface ==
                   surface_ptr->wlr_surface_ptr) {
            wlr_seat_keyboard_clear_focus(surface_ptr->wlr_seat_ptr);
        }
    }

    if (NULL != surface_ptr->super_element.parent_container_ptr) {
        wlmtk_container_set_keyboard_focus_element(
            surface_ptr->super_element.parent_container_ptr,
            &surface_ptr->super_element,
            activated);
    }
    surface_ptr->activated = activated;
}

/* ------------------------------------------------------------------------- */
bool wlmtk_surface_is_activated(wlmtk_surface_t *surface_ptr)
{
    return surface_ptr->activated;
}

/* ------------------------------------------------------------------------- */
void wlmtk_surface_connect_map_listener_signal(
    wlmtk_surface_t *surface_ptr,
    struct wl_listener *listener_ptr,
    wl_notify_func_t handler)
{
    if (NULL == surface_ptr->wlr_surface_ptr) return;
    wlmtk_util_connect_listener_signal(
        &surface_ptr->wlr_surface_ptr->events.map,
        listener_ptr,
        handler);
}

/* ------------------------------------------------------------------------- */
void wlmtk_surface_connect_unmap_listener_signal(
    wlmtk_surface_t *surface_ptr,
    struct wl_listener *listener_ptr,
    wl_notify_func_t handler)
{
    if (NULL == surface_ptr->wlr_surface_ptr) return;
    wlmtk_util_connect_listener_signal(
        &surface_ptr->wlr_surface_ptr->events.unmap,
        listener_ptr,
        handler);
}

/* == Local (static) methods =============================================== */

/* ------------------------------------------------------------------------- */
/**
 * Initializes the surface.
 *
 * @param surface_ptr
 * @param wlr_surface_ptr
 *
 * @return true on success.
 */
bool _wlmtk_surface_init(
    wlmtk_surface_t *surface_ptr,
    struct wlr_surface *wlr_surface_ptr)
{
    BS_ASSERT(NULL != surface_ptr);
    *surface_ptr = (wlmtk_surface_t){};

    if (!wlmtk_element_init(&surface_ptr->super_element)) {
        _wlmtk_surface_fini(surface_ptr);
        return false;
    }
    surface_ptr->orig_super_element_vmt = wlmtk_element_extend(
        &surface_ptr->super_element, &surface_element_vmt);
    wlmtk_util_connect_listener_signal(
        &surface_ptr->super_element.events.pointer_leave,
        &surface_ptr->element_pointer_leave_listener,
        _wlmtk_surface_handle_element_pointer_leave);
    wlmtk_util_connect_listener_signal(
        &surface_ptr->super_element.events.pointer_motion,
        &surface_ptr->element_pointer_motion_listener,
        _wlmtk_surface_handle_element_pointer_motion);

    surface_ptr->wlr_surface_ptr = wlr_surface_ptr;
    if (NULL != surface_ptr->wlr_surface_ptr) {
        surface_ptr->wlr_surface_ptr->data = surface_ptr;
        wlmtk_util_connect_listener_signal(
            &wlr_surface_ptr->events.commit,
            &surface_ptr->surface_commit_listener,
            _wlmtk_surface_handle_surface_commit);
        wlmtk_util_connect_listener_signal(
            &wlr_surface_ptr->events.map,
            &surface_ptr->surface_map_listener,
            _wlmtk_surface_handle_surface_map);
        wlmtk_util_connect_listener_signal(
            &wlr_surface_ptr->events.unmap,
            &surface_ptr->surface_unmap_listener,
            _wlmtk_surface_handle_surface_unmap);
        surface_ptr->wlr_surface_ptr->data = surface_ptr;
    }

    return true;
}

/* ------------------------------------------------------------------------- */
/**
 * Initializes the surface, based off an XDG surface.
 *
 * TODO(kaeser@gubbe.ch): Merge with _wlmtk_surface_init.
 *
 * @param surface_ptr
 * @param wlr_xdg_surface_ptr
 *
 * @return true on success.
 */
bool _wlmtk_xdg_surface_init(
    wlmtk_surface_t *surface_ptr,
    struct wlr_xdg_surface *wlr_xdg_surface_ptr)
{
    BS_ASSERT(NULL != surface_ptr);
    *surface_ptr = (wlmtk_surface_t){};

    if (!wlmtk_element_init(&surface_ptr->super_element)) {
        _wlmtk_surface_fini(surface_ptr);
        return false;
    }
    surface_ptr->orig_super_element_vmt = wlmtk_element_extend(
        &surface_ptr->super_element, &surface_element_vmt);
    wlmtk_util_connect_listener_signal(
        &surface_ptr->super_element.events.pointer_leave,
        &surface_ptr->element_pointer_leave_listener,
        _wlmtk_surface_handle_element_pointer_leave);
    wlmtk_util_connect_listener_signal(
        &surface_ptr->super_element.events.pointer_motion,
        &surface_ptr->element_pointer_motion_listener,
        _wlmtk_surface_handle_element_pointer_motion);

    surface_ptr->wlr_xdg_surface_ptr = wlr_xdg_surface_ptr;
    surface_ptr->wlr_surface_ptr = wlr_xdg_surface_ptr->surface;
    if (NULL != surface_ptr->wlr_surface_ptr) {
        surface_ptr->wlr_surface_ptr->data = surface_ptr;
        wlmtk_util_connect_listener_signal(
            &surface_ptr->wlr_surface_ptr->events.commit,
            &surface_ptr->surface_commit_listener,
            _wlmtk_surface_handle_surface_commit);
        wlmtk_util_connect_listener_signal(
            &surface_ptr->wlr_surface_ptr->events.map,
            &surface_ptr->surface_map_listener,
            _wlmtk_surface_handle_surface_map);
        wlmtk_util_connect_listener_signal(
            &surface_ptr->wlr_surface_ptr->events.unmap,
            &surface_ptr->surface_unmap_listener,
            _wlmtk_surface_handle_surface_unmap);
        surface_ptr->wlr_surface_ptr->data = surface_ptr;
    }

    return true;
}

/* ------------------------------------------------------------------------- */
/**
 * Un-initializes the surface.
 *
 * @param surface_ptr
 */
void _wlmtk_surface_fini(wlmtk_surface_t *surface_ptr)
{
    if (NULL != surface_ptr->wlr_scene_tree_ptr) {
        wlmtk_util_disconnect_listener(
            &surface_ptr->wlr_scene_tree_node_destroy_listener);
    }

    if (NULL != surface_ptr->wlr_surface_ptr) {
        surface_ptr->wlr_surface_ptr->data = NULL;
        surface_ptr->wlr_surface_ptr = NULL;
        wlmtk_util_disconnect_listener(&surface_ptr->surface_commit_listener);
        wlmtk_util_disconnect_listener(&surface_ptr->surface_map_listener);
        wlmtk_util_disconnect_listener(&surface_ptr->surface_unmap_listener);
    }

    wlmtk_util_disconnect_listener(
        &surface_ptr->element_pointer_motion_listener);
    wlmtk_util_disconnect_listener(
        &surface_ptr->element_pointer_leave_listener);
    wlmtk_element_fini(&surface_ptr->super_element);
    *surface_ptr = (wlmtk_surface_t){};
}

/* ------------------------------------------------------------------------- */
/** Implements @ref wlmtk_element_vmt_t::destroy. Calls the dtor. */
void _wlmtk_surface_element_destroy(wlmtk_element_t *element_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        element_ptr, wlmtk_surface_t, super_element);
    wlmtk_surface_destroy(surface_ptr);
}

/* ------------------------------------------------------------------------- */
/**
 * Implements @ref wlmtk_element_vmt_t::create_scene_node. Creates the node.
 *
 * @param element_ptr
 * @param wlr_scene_tree_ptr
 *
 * @return The scene graph API node of the node displaying the surface and all
 *     of it's sub-surfaces. Or NULL on error.
 */
struct wlr_scene_node *_wlmtk_surface_element_create_scene_node(
    wlmtk_element_t *element_ptr,
    struct wlr_scene_tree *wlr_scene_tree_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        element_ptr, wlmtk_surface_t, super_element);
    BS_ASSERT(NULL == surface_ptr->wlr_scene_tree_ptr);

    if (NULL != surface_ptr->wlr_xdg_surface_ptr) {
        surface_ptr->wlr_scene_tree_ptr = wlr_scene_xdg_surface_create(
            wlr_scene_tree_ptr, surface_ptr->wlr_xdg_surface_ptr);
    } else {
        surface_ptr->wlr_scene_tree_ptr = wlr_scene_subsurface_tree_create(
            wlr_scene_tree_ptr, surface_ptr->wlr_surface_ptr);
    }
    if (NULL == surface_ptr->wlr_scene_tree_ptr) return NULL;

    wlmtk_util_connect_listener_signal(
        &surface_ptr->wlr_scene_tree_ptr->node.events.destroy,
        &surface_ptr->wlr_scene_tree_node_destroy_listener,
        _wlmtk_surface_handle_wlr_scene_tree_node_destroy);
    return &surface_ptr->wlr_scene_tree_ptr->node;
}

/* ------------------------------------------------------------------------- */
/**
 * Implementation of the element's get_dimensions method: Return dimensions.
 *
 * @param element_ptr
 * @param left_ptr            Leftmost position. May be NULL.
 * @param top_ptr             Topmost position. May be NULL.
 * @param right_ptr           Rightmost position. Ma be NULL.
 * @param bottom_ptr          Bottommost position. May be NULL.
 */
void _wlmtk_surface_element_get_dimensions(
    wlmtk_element_t *element_ptr,
    int *left_ptr,
    int *top_ptr,
    int *right_ptr,
    int *bottom_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        element_ptr, wlmtk_surface_t, super_element);

    if (NULL != left_ptr) *left_ptr = 0;
    if (NULL != top_ptr) *top_ptr = 0;
    if (NULL != right_ptr) *right_ptr = surface_ptr->committed_width;
    if (NULL != bottom_ptr) *bottom_ptr = surface_ptr->committed_height;
}

/* ------------------------------------------------------------------------- */
/**
 * Implements @ref wlmtk_element_vmt_t::pointer_accepts_motion.
 *
 * @param element_ptr
 * @param motion_event_ptr
 *
 * @return true if there is a surface (or sub-surface) at the given coordinates
 * that will accept the pointer's movement.
 */
bool _wlmtk_surface_element_pointer_accepts_motion(
    wlmtk_element_t *element_ptr,
    wlmtk_pointer_motion_event_t *motion_event_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        element_ptr, wlmtk_surface_t, super_element);

    if (NULL == surface_ptr->super_element.wlr_scene_node_ptr) return false;

    // Get the layout local coordinates of the node, so we can adjust the
    // node-local (x, y) for the `wlr_scene_node_at` call.
    int lx, ly;
    if (!wlr_scene_node_coords(
            surface_ptr->super_element.wlr_scene_node_ptr, &lx, &ly)) {
        return false;
    }
    // Get the node below the cursor. Return if there's no buffer node.
    double node_x, node_y;
    struct wlr_scene_node *wlr_scene_node_ptr = wlr_scene_node_at(
        surface_ptr->super_element.wlr_scene_node_ptr,
        motion_event_ptr->x + lx,
        motion_event_ptr->y + ly,
        &node_x,
        &node_y);

    return (NULL != wlr_scene_node_ptr &&
            WLR_SCENE_NODE_BUFFER == wlr_scene_node_ptr->type);
}

/* ------------------------------------------------------------------------- */
/**
 * Passes pointer button event further to the focused surface, if any.
 *
 * The actual passing is handled by `wlr_seat`. Here we just verify that the
 * currently-focused surface (or sub-surface) is part of this surface.
 *
 * @param element_ptr
 * @param button_event_ptr
 *
 * @return Whether the button event was consumed.
 */
bool _wlmtk_surface_element_pointer_button(
    wlmtk_element_t *element_ptr,
    const wlmtk_button_event_t *button_event_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        element_ptr, wlmtk_surface_t, super_element);

    // Complain if the surface isn't part of our responsibility.
    struct wlr_surface *focused_wlr_surface_ptr =
        surface_ptr->wlr_seat_ptr->pointer_state.focused_surface;
    if (NULL == focused_wlr_surface_ptr) return false;
    // TODO(kaeser@gubbe.ch): Dragging the pointer from an activated window
    // over to a non-activated window will trigger the condition here on the
    // WLMTK_BUTTON_UP event. Needs a test and fixing.
    // Additionally, this appears to trigger when creating a new XWL popup and
    // the UP event goes to the new surface. Also needs test & fixing.
    if (WLMTK_BUTTON_UP != button_event_ptr->type) {
        BS_ASSERT(surface_ptr->wlr_surface_ptr ==
                  wlr_surface_get_root_surface(focused_wlr_surface_ptr));
    }

    // We're only forwarding PRESSED & RELEASED events.
    if (WLMTK_BUTTON_DOWN == button_event_ptr->type ||
        WLMTK_BUTTON_UP == button_event_ptr->type) {
        enum wl_pointer_button_state state =
            (button_event_ptr->type == WLMTK_BUTTON_DOWN) ?
            WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED;
        wlr_seat_pointer_notify_button(
            surface_ptr->wlr_seat_ptr,
            button_event_ptr->time_msec,
            button_event_ptr->button,
            state);
    }
    return true;
}

/* ------------------------------------------------------------------------- */
/**
 * Passes pointer axis events further to the focused surface, if any.
 *
 * The actual passing is handled by `wlr_seat`. Here we just verify that the
 * currently-focused surface (or sub-surface) is part of this surface.
 *
 * @param element_ptr
 * @param wlr_pointer_axis_event_ptr
 *
 * @return Whether the axis event was consumed.
 */
bool _wlmtk_surface_element_pointer_axis(
        wlmtk_element_t *element_ptr,
        struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        element_ptr, wlmtk_surface_t, super_element);

    // Complain if the surface isn't part of our responsibility.
    struct wlr_surface *focused_wlr_surface_ptr =
        surface_ptr->wlr_seat_ptr->pointer_state.focused_surface;
    if (NULL == focused_wlr_surface_ptr) return false;

    wlr_seat_pointer_notify_axis(
        surface_ptr->wlr_seat_ptr,
        wlr_pointer_axis_event_ptr->time_msec,
        wlr_pointer_axis_event_ptr->orientation,
        wlr_pointer_axis_event_ptr->delta,
        wlr_pointer_axis_event_ptr->delta_discrete,
        wlr_pointer_axis_event_ptr->source
        , wlr_pointer_axis_event_ptr->relative_direction
        );
    return true;
}

/* ------------------------------------------------------------------------- */
/**
 * Implements @ref wlmtk_element_vmt_t::keyboard_event. Handle keyboard events.
 *
 * Registers the surface as active, and forwards events there.
 *
 * @param element_ptr
 * @param wlr_keyboard_key_event_ptr
 *
 * @return true if the axis event was handled.
 */
bool _wlmtk_surface_element_keyboard_event(
    wlmtk_element_t *element_ptr,
    struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        element_ptr, wlmtk_surface_t, super_element);

    if (!surface_ptr->activated) return false;

    // Guard clauses.
    if (NULL == surface_ptr->wlr_seat_ptr) return false;

    wlr_seat_keyboard_notify_key(
        surface_ptr->wlr_seat_ptr,
        wlr_keyboard_key_event_ptr->time_msec,
        wlr_keyboard_key_event_ptr->keycode,
        wlr_keyboard_key_event_ptr->state);
    return true;
}

/* ------------------------------------------------------------------------- */
/**
 * Handler for the `destroy` signal of `wlr_scene_tree_ptr->node`.
 *
 * We have this registered to clear out the extra pointer we're holding to
 * @ref wlmtk_surface_t::wlr_scene_tree_ptr. @ref wlmtk_element_t has a
 * separate destroy handler that will take care of actual cleanup.
 * */
void _wlmtk_surface_handle_wlr_scene_tree_node_destroy(
    struct wl_listener *listener_ptr,
    __UNUSED__ void *data_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmtk_surface_t, wlr_scene_tree_node_destroy_listener);
    surface_ptr->wlr_scene_tree_ptr = NULL;
    wlmtk_util_disconnect_listener(&surface_ptr->wlr_scene_tree_node_destroy_listener);
}

/* ------------------------------------------------------------------------- */
/** Handler for the `commit` signal of `wlr_surface`. */
void _wlmtk_surface_handle_surface_commit(
    struct wl_listener *listener_ptr,
    __UNUSED__ void *data_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmtk_surface_t, surface_commit_listener);

    _wlmtk_surface_commit_size(
        surface_ptr,
        surface_ptr->wlr_surface_ptr->current.width,
        surface_ptr->wlr_surface_ptr->current.height);
}

/* ------------------------------------------------------------------------- */
/**
 * Handles the `surface_map` signal: Makes the surface visible.
 *
 * @param listener_ptr
 * @param data_ptr
 */
void _wlmtk_surface_handle_surface_map(
    struct wl_listener *listener_ptr,
    __UNUSED__ void *data_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmtk_surface_t, surface_map_listener);
    wlmtk_element_set_visible(wlmtk_surface_element(surface_ptr), true);
}

/* ------------------------------------------------------------------------- */
/**
 * Handles the `surface_unmap` signal: Makes the surface invisible.
 *
 * @param listener_ptr
 * @param data_ptr
 */
void _wlmtk_surface_handle_surface_unmap(
    struct wl_listener *listener_ptr,
    __UNUSED__ void *data_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmtk_surface_t, surface_unmap_listener);
    wlmtk_element_set_visible( wlmtk_surface_element(surface_ptr), false);
}

/* ------------------------------------------------------------------------- */
/**
 * Handles pointer leave: If there's a WLR (sub)surface currently holding
 * focus, that will be cleared.
 *
 * @param listener_ptr
 * @param data_ptr
 */
void _wlmtk_surface_handle_element_pointer_leave(
    struct wl_listener *listener_ptr,
    __UNUSED__ void *data_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmtk_surface_t, element_pointer_leave_listener);

    // Guard clause.
    if (NULL == surface_ptr->wlr_seat_ptr) return;

    // If the current surface's parent is our surface: clear it.
    struct wlr_surface *focused_wlr_surface_ptr =
        surface_ptr->wlr_seat_ptr->pointer_state.focused_surface;
    if (NULL != focused_wlr_surface_ptr &&
        wlr_surface_get_root_surface(focused_wlr_surface_ptr) ==
        surface_ptr->wlr_surface_ptr) {
        wlr_seat_pointer_clear_focus(surface_ptr->wlr_seat_ptr);
    }
}

/* ------------------------------------------------------------------------- */
/**
 * Handles pointer motion: Passes the motion events to client's surface(s).
 *
 * Identifies the surface (or sub-surface) at the given coordinates, and pass
 * on the motion event to that surface. If needed, will update the seat's
 * pointer focus.
 *
 * @param listener_ptr
 * @param data_ptr            Points to a @ref wlmtk_pointer_motion_event_t.
 */
void _wlmtk_surface_handle_element_pointer_motion(
    struct wl_listener *listener_ptr,
    void *data_ptr)
{
    wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmtk_surface_t, element_pointer_motion_listener);
    wlmtk_pointer_motion_event_t *motion_event_ptr = data_ptr;

    if (NULL == surface_ptr->super_element.wlr_scene_node_ptr) return;

    // Get the layout local coordinates of the node, so we can adjust the
    // node-local (x, y) for the `wlr_scene_node_at` call.
    int lx, ly;
    if (!wlr_scene_node_coords(
            surface_ptr->super_element.wlr_scene_node_ptr, &lx, &ly)) {
        return;
    }
    // Get the node below the cursor. Return if there's no buffer node.
    double node_x, node_y;
    struct wlr_scene_node *wlr_scene_node_ptr = wlr_scene_node_at(
        surface_ptr->super_element.wlr_scene_node_ptr,
        motion_event_ptr->x + lx,
        motion_event_ptr->y + ly,
        &node_x,
        &node_y);

    if (NULL == wlr_scene_node_ptr ||
        WLR_SCENE_NODE_BUFFER != wlr_scene_node_ptr->type) {
        return;
    }

    struct wlr_scene_buffer *wlr_scene_buffer_ptr =
        wlr_scene_buffer_from_node(wlr_scene_node_ptr);
    struct wlr_scene_surface *wlr_scene_surface_ptr =
        wlr_scene_surface_try_from_buffer(wlr_scene_buffer_ptr);
    if (NULL == wlr_scene_surface_ptr) return;

    BS_ASSERT(surface_ptr->wlr_surface_ptr ==
              wlr_surface_get_root_surface(wlr_scene_surface_ptr->surface));
    if (NULL != surface_ptr->wlr_seat_ptr) {
        wlr_seat_pointer_notify_enter(
            surface_ptr->wlr_seat_ptr,
            wlr_scene_surface_ptr->surface,
            node_x, node_y);
        wlr_seat_pointer_notify_motion(
            surface_ptr->wlr_seat_ptr,
            motion_event_ptr->time_msec,
            node_x, node_y);
    }
}

/* ------------------------------------------------------------------------- */
/**
 * Surface commits a new size: Store the size, and update the parent's layout.
 *
 * @param surface_ptr
 * @param width
 * @param height
 */
void _wlmtk_surface_commit_size(
    wlmtk_surface_t *surface_ptr,
    int width,
    int height)
{
    if (surface_ptr->committed_width != width ||
        surface_ptr->committed_height != height) {
        surface_ptr->committed_width = width;
        surface_ptr->committed_height = height;
    }

    if (NULL != surface_ptr->super_element.parent_container_ptr) {
        wlmtk_container_invalidate_layout(
            surface_ptr->super_element.parent_container_ptr);
    }
}


/* == Unit tests =========================================================== */

static void test_create_destroy(bs_test_t *test_ptr);
static void test_dimensions(bs_test_t *test_ptr);

/** Test cases */
static const bs_test_case_t _wlmtk_surface_test_cases[] = {
    { 1, "create_destroy", test_create_destroy },
    { 1, "dimensions", test_dimensions },
    BS_TEST_CASE_SENTINEL()
};

const bs_test_set_t wlmtk_surface_test_set = BS_TEST_SET(
    true, "surface", _wlmtk_surface_test_cases);

/* ------------------------------------------------------------------------- */
/** Tests ctor and dtor. */
void test_create_destroy(bs_test_t *test_ptr)
{
    wlmtk_surface_t *surface_ptr = wlmtk_surface_create(NULL, NULL);
    BS_TEST_VERIFY_NEQ(test_ptr, NULL, surface_ptr);

    BS_TEST_VERIFY_EQ(
        test_ptr,
        &surface_ptr->super_element,
        wlmtk_surface_element(surface_ptr));

    wlmtk_surface_destroy(surface_ptr);
}

/* ------------------------------------------------------------------------- */
/** Verifies that dimensions from the surface propagate thorugh. */
void test_dimensions(bs_test_t *test_ptr)
{
    struct wlr_surface wlr_surface = {};
    wl_list_init(&wlr_surface.current.subsurfaces_below);
    wl_list_init(&wlr_surface.current.subsurfaces_above);
    wl_signal_init(&wlr_surface.events.commit);
    wl_signal_init(&wlr_surface.events.destroy);
    wl_signal_init(&wlr_surface.events.map);
    wl_signal_init(&wlr_surface.events.unmap);

    wlmtk_surface_t *s = wlmtk_surface_create(&wlr_surface, NULL);
    BS_TEST_VERIFY_NEQ(test_ptr, NULL, s);

    WLMTK_TEST_VERIFY_WLRBOX_EQ(
        test_ptr, 0, 0, 0, 0,
        wlmtk_element_get_dimensions_box(wlmtk_surface_element(s)));

    wlr_surface.current.width = 640;
    wlr_surface.current.height = 480;
    wl_signal_emit(&wlr_surface.events.commit, NULL);
    WLMTK_TEST_VERIFY_WLRBOX_EQ(
        test_ptr, 0, 0, 640, 480,
        wlmtk_element_get_dimensions_box(wlmtk_surface_element(s)));

    wlmtk_surface_destroy(s);
}

/* == End of surface.c ===================================================== */
