/* ========================================================================= */
/**
 * @file server.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 "server.h"

#include <libbase/libbase.h>
#include <libbase/plist.h>
#include <linux/input-event-codes.h>
#include <stdlib.h>
#define WLR_USE_UNSTABLE
#include <wlr/backend.h>
#include <wlr/types/wlr_viewporter.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_fractional_scale_v1.h>
#include <wlr/types/wlr_data_control_v1.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_primary_selection.h>
#include <wlr/types/wlr_primary_selection_v1.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_scene.h>
#undef WLR_USE_UNSTABLE

#include "background.h"
#include "input/manager.h"
#include "task_list.h"
#include "toolkit/toolkit.h"

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

/** Name of the "seat". */
static const char             *seat_name_ptr = "seat0";

static void _wlmaker_server_unclaimed_button_event_handler(
    struct wl_listener *listener_ptr,
    void *data_ptr);
static void _wlmaker_server_handle_input_activity(
    struct wl_listener *listener_ptr,
    void *data_ptr);
static void _wlmaker_server_handle_deactivate_task_list(
    struct wl_listener *listener_ptr,
    void *data_ptr);
static void handle_request_set_selection(
    struct wl_listener *listener_ptr,
    void *data_ptr);
static void handle_request_set_primary_selection(
    struct wl_listener *listener_ptr,
    void *data_ptr);

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

/* ------------------------------------------------------------------------- */
wlmaker_server_t *wlmaker_server_create(
    bspl_dict_t *config_dict_ptr,
    wlmaker_files_t *files_ptr,
    wlmaker_config_style_t *style_ptr,
    const wlmaker_server_options_t *options_ptr)
{
    wlmaker_server_t *server_ptr = logged_calloc(1, sizeof(wlmaker_server_t));
    if (NULL == server_ptr) return NULL;
    server_ptr->options_ptr = options_ptr;
    server_ptr->files_ptr = files_ptr;
    server_ptr->style_ptr = style_ptr;

    server_ptr->config_dict_ptr = bspl_dict_ref(config_dict_ptr);
    if (NULL == server_ptr->config_dict_ptr) {
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    wl_signal_init(&server_ptr->task_list_enabled_event);
    wl_signal_init(&server_ptr->task_list_disabled_event);

    wl_signal_init(&server_ptr->window_created_event);
    wl_signal_init(&server_ptr->window_destroyed_event);
    wl_signal_init(&server_ptr->theme_changed_event);

    // Prepare display and socket.
    server_ptr->wl_display_ptr = wl_display_create();
    if (NULL == server_ptr->wl_display_ptr) {
        bs_log(BS_ERROR, "Failed wl_display_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }
    server_ptr->wl_socket_name_ptr = wl_display_add_socket_auto(
        server_ptr->wl_display_ptr);
    if (NULL == server_ptr->wl_socket_name_ptr) {
        bs_log(BS_ERROR, "Failed wl_display_add_socket_auto()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    server_ptr->wlr_viewporter_ptr = wlr_viewporter_create(
        server_ptr->wl_display_ptr);
    if (NULL == server_ptr->wlr_viewporter_ptr) {
        bs_log(BS_ERROR, "Failed wlr_viewporter_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    server_ptr->wlr_fractional_scale_manager_ptr =
        wlr_fractional_scale_manager_v1_create(server_ptr->wl_display_ptr, 1);
    if (NULL == server_ptr->wlr_fractional_scale_manager_ptr) {
        bs_log(BS_ERROR, "Failed wlr_fractional_scale_manager_v1_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    // Configure the seat, which is the potential set of input devices operated
    // by one user at a computer's "seat".
    server_ptr->wlr_seat_ptr = wlr_seat_create(
        server_ptr->wl_display_ptr, seat_name_ptr);
    if (NULL == server_ptr->wlr_seat_ptr) {
        bs_log(BS_ERROR, "Failed wlr_seat_create()");
        wlmaker_server_destroy(server_ptr);
    }

    // The scene graph.
    server_ptr->wlr_scene_ptr = wlr_scene_create();
    if (NULL == server_ptr->wlr_scene_ptr) {
        bs_log(BS_ERROR, "Failed wlr_scene_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    server_ptr->wlr_output_layout_ptr = wlr_output_layout_create(
        server_ptr->wl_display_ptr);
    if (NULL == server_ptr->wlr_output_layout_ptr) {
        bs_log(BS_ERROR, "Failed wlr_output_layout_create(%p)",
               server_ptr->wl_display_ptr);
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    char *f = wlmaker_files_xdg_config_fname(
        server_ptr->files_ptr, "OutputState.plist");
    if (NULL == f) {
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }
    server_ptr->backend_ptr = wlmbe_backend_create(
        server_ptr->wl_display_ptr,
        server_ptr->wlr_scene_ptr,
        server_ptr->wlr_output_layout_ptr,
        server_ptr->options_ptr->width,
        server_ptr->options_ptr->height,
        server_ptr->config_dict_ptr,
        f);
    free(f);
    if (NULL == server_ptr->backend_ptr) {
        bs_log(BS_ERROR, "Failed wlmbe_backend_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    // Root element.
    server_ptr->root_ptr = wlmtk_root_create(
        server_ptr->wlr_scene_ptr,
        server_ptr->wlr_output_layout_ptr);
    if (NULL == server_ptr->root_ptr) {
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }
    wlmtk_util_connect_listener_signal(
        &wlmtk_root_events(server_ptr->root_ptr)->unclaimed_button_event,
        &server_ptr->unclaimed_button_event_listener,
        _wlmaker_server_unclaimed_button_event_handler);

    server_ptr->input_manager_ptr = wlmim_input_manager_create(
        wlmbe_backend_wlr(server_ptr->backend_ptr),
        server_ptr->wlr_output_layout_ptr,
        server_ptr->wlr_seat_ptr,
        server_ptr->config_dict_ptr,
        &style_ptr->cursor,
        server_ptr->root_ptr);
    if (NULL == server_ptr->input_manager_ptr) {
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }
    wlmtk_util_connect_listener_signal(
        &wlmim_events(server_ptr->input_manager_ptr)->deactivate_task_list,
        &server_ptr->deactivate_task_list_listener,
        _wlmaker_server_handle_deactivate_task_list);
    wlmtk_util_connect_listener_signal(
        &wlmim_events(server_ptr->input_manager_ptr)->activity,
        &server_ptr->input_activity_listener,
        _wlmaker_server_handle_input_activity);

    // Session lock manager.
    server_ptr->lock_mgr_ptr = wlmaker_lock_mgr_create(server_ptr);
    if (NULL == server_ptr->lock_mgr_ptr) {
        bs_log(BS_ERROR, "Failed wlmaker_lock_mgr_create(%p)", server_ptr);
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    // Idle monitor.
    server_ptr->idle_monitor_ptr = wlmaker_idle_monitor_create(server_ptr);
    if (NULL == server_ptr->idle_monitor_ptr) {
        bs_log(BS_ERROR, "Failed wlmaker_idle_monitor_create(%p)", server_ptr);
        return NULL;
    }

    // The below clipboard-related helpers all setup a listener |display_destroy|
    // for freeing the assets held via the respective create() calls. Hence no
    // need to call a clean-up method from our end.
    server_ptr->wlr_data_device_manager_ptr = wlr_data_device_manager_create(
        server_ptr->wl_display_ptr);
    if (NULL == server_ptr->wlr_data_device_manager_ptr) {
        bs_log(BS_ERROR, "Failed wlr_data_device_manager_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }
    server_ptr->wlr_primary_selection_v1_device_manager_ptr =
        wlr_primary_selection_v1_device_manager_create(
            server_ptr->wl_display_ptr);
    if (NULL == server_ptr->wlr_primary_selection_v1_device_manager_ptr) {
        bs_log(BS_ERROR, "Failed wlr_primary_selection_v1_device_manager_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }
    server_ptr->wlr_data_control_manager_v1_ptr =
        wlr_data_control_manager_v1_create(server_ptr->wl_display_ptr);
    if (NULL == server_ptr->wlr_data_control_manager_v1_ptr) {
        bs_log(BS_ERROR, "Failed wlr_data_control_manager_v1_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    wlmtk_util_connect_listener_signal(
        &server_ptr->wlr_seat_ptr->events.request_set_selection,
        &server_ptr->request_set_selection_listener,
        handle_request_set_selection);
    wlmtk_util_connect_listener_signal(
        &server_ptr->wlr_seat_ptr->events.request_set_primary_selection,
        &server_ptr->request_set_primary_selection_listener,
        handle_request_set_primary_selection);

    server_ptr->xdg_shell_ptr = wlmaker_xdg_shell_create(server_ptr);
    if (NULL == server_ptr->xdg_shell_ptr) {
        bs_log(BS_ERROR, "Failed wlmaker_xdg_shell_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    server_ptr->xdg_decoration_manager_ptr =
        wlmaker_xdg_decoration_manager_create(
            server_ptr->wl_display_ptr,
            server_ptr->config_dict_ptr);
    if (NULL == server_ptr->xdg_decoration_manager_ptr) {
        bs_log(BS_ERROR,
               "Failed wlmaker_xdg_decoration_manager_create(%p, %p)",
               server_ptr->wl_display_ptr,
               server_ptr->config_dict_ptr);
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    server_ptr->layer_shell_ptr = wlmaker_layer_shell_create(server_ptr);
    if (NULL == server_ptr->layer_shell_ptr) {
        bs_log(BS_ERROR, "Failed wlmaker_layer_shell_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    server_ptr->icon_manager_ptr = wlmaker_icon_manager_create(
        server_ptr->wl_display_ptr, server_ptr);
    if (NULL == server_ptr->icon_manager_ptr) {
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    server_ptr->input_observation_manager_ptr =
        wlmaker_input_observation_manager_create(
            server_ptr->wl_display_ptr,
            server_ptr->wlr_seat_ptr,
            wlmim_wlr_cursor(server_ptr->input_manager_ptr));
    if (NULL == server_ptr->input_observation_manager_ptr) {
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    if (server_ptr->options_ptr->start_xwayland) {
        server_ptr->xwl_ptr = wlmaker_xwl_create(server_ptr);
        if (NULL == server_ptr->xwl_ptr) {
            wlmaker_server_destroy(server_ptr);
            return NULL;
        }
    }

    server_ptr->monitor_ptr = wlmaker_subprocess_monitor_create(server_ptr);
    if (NULL == server_ptr->monitor_ptr) {
        bs_log(BS_ERROR, "Failed wlmaker_subprocess_monitor_create()");
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    server_ptr->corner_ptr = wlmaker_corner_create(
        bspl_dict_get_dict(server_ptr->config_dict_ptr, "HotCorner"),
        wl_display_get_event_loop(server_ptr->wl_display_ptr),
        server_ptr->wlr_output_layout_ptr,
        wlmim_wlr_cursor(server_ptr->input_manager_ptr),
        &wlmim_events(server_ptr->input_manager_ptr)->cursor_position_updated,
        server_ptr);
    if (NULL == server_ptr->corner_ptr) {
        bs_log(BS_ERROR, "Failed wlmaker_corner_create(%p, %p, %p, %p, %p, %p)",
               bspl_dict_get_dict(server_ptr->config_dict_ptr, "HotCorner"),
               wl_display_get_event_loop(server_ptr->wl_display_ptr),
               server_ptr->wlr_output_layout_ptr,
               wlmim_wlr_cursor(server_ptr->input_manager_ptr),
               &wlmim_events(server_ptr->input_manager_ptr)->cursor_position_updated,
               server_ptr);
        wlmaker_server_destroy(server_ptr);
        return NULL;
    }

    return server_ptr;
}

/* ------------------------------------------------------------------------- */
void wlmaker_server_destroy(wlmaker_server_t *server_ptr)
{
    bs_dllist_for_each(
        &server_ptr->backgrounds,
        wlmaker_background_dlnode_destroy,
        NULL);

    if (NULL != server_ptr->root_menu_ptr) {
        wlmaker_root_menu_destroy(server_ptr->root_menu_ptr);
        server_ptr->root_menu_ptr = NULL;
    }

    // We don't destroy a few of the handlers, since wlroots will crash if
    // they are destroyed -- and apparently, wlroots cleans them up anyway.
    // These are:
    // * server_ptr->wlr_seat_ptr
    // * server_ptr->wlr_backend_ptr
    // * server_ptr->wlr_scene_ptr  (there is no "destroy" function)
    if (NULL != server_ptr->corner_ptr) {
        wlmaker_corner_destroy(server_ptr->corner_ptr);
        server_ptr->corner_ptr = NULL;
    }

    if (NULL != server_ptr->monitor_ptr) {
        wlmaker_subprocess_monitor_destroy(server_ptr->monitor_ptr);
        server_ptr->monitor_ptr =NULL;
    }

    if (NULL != server_ptr->xwl_ptr) {
        wlmaker_xwl_destroy(server_ptr->xwl_ptr);
        server_ptr->xwl_ptr = NULL;
    }

    if (NULL != server_ptr->input_observation_manager_ptr) {
        wlmaker_input_observation_manager_destroy(
            server_ptr->input_observation_manager_ptr);
        server_ptr->input_observation_manager_ptr = NULL;
    }

    if (NULL != server_ptr->icon_manager_ptr) {
        wlmaker_icon_manager_destroy(server_ptr->icon_manager_ptr);
        server_ptr->icon_manager_ptr = NULL;
    }

    if (NULL != server_ptr->layer_shell_ptr) {
        wlmaker_layer_shell_destroy(server_ptr->layer_shell_ptr);
        server_ptr->layer_shell_ptr = NULL;
    }

    if (NULL != server_ptr->xdg_decoration_manager_ptr) {
        wlmaker_xdg_decoration_manager_destroy(
            server_ptr->xdg_decoration_manager_ptr);
        server_ptr->xdg_decoration_manager_ptr = NULL;
    }

    if (NULL != server_ptr->xdg_shell_ptr) {
        wlmaker_xdg_shell_destroy(server_ptr->xdg_shell_ptr);
        server_ptr->xdg_shell_ptr = NULL;
    }

    if (NULL != server_ptr->wl_display_ptr) {
        wl_display_destroy_clients(server_ptr->wl_display_ptr);
        server_ptr->wl_display_ptr = NULL;
    }

    if (NULL != server_ptr->idle_monitor_ptr) {
        wlmaker_idle_monitor_destroy(server_ptr->idle_monitor_ptr);
        server_ptr->idle_monitor_ptr = NULL;
    }

    if (NULL != server_ptr->lock_mgr_ptr) {
        wlmaker_lock_mgr_destroy(server_ptr->lock_mgr_ptr);
        server_ptr->lock_mgr_ptr = NULL;
    }

    if (NULL != server_ptr->input_manager_ptr) {
        wlmtk_util_disconnect_listener(
            &server_ptr->deactivate_task_list_listener);
        wlmtk_util_disconnect_listener(
            &server_ptr->input_activity_listener);
        wlmim_input_manager_destroy(server_ptr->input_manager_ptr);
        server_ptr->input_manager_ptr = NULL;
    }

    if (NULL != server_ptr->root_ptr) {
        wlmtk_util_disconnect_listener(
            &server_ptr->unclaimed_button_event_listener);
        wlmtk_root_destroy(server_ptr->root_ptr);
        server_ptr->root_ptr = NULL;
    }

    if (NULL != server_ptr->backend_ptr) {
        wlmbe_backend_destroy(server_ptr->backend_ptr);
        server_ptr->backend_ptr = NULL;
    }

    if (NULL != server_ptr->wl_display_ptr) {
        wl_display_destroy(server_ptr->wl_display_ptr);
        server_ptr->wl_display_ptr = NULL;
    }

    if (NULL != server_ptr->config_dict_ptr) {
        bspl_dict_unref(server_ptr->config_dict_ptr);
        server_ptr->config_dict_ptr = NULL;
    }

    if (NULL != server_ptr->files_ptr) {
        wlmaker_files_destroy(server_ptr->files_ptr);
        server_ptr->files_ptr = NULL;
    }

    free(server_ptr);
}

/* ------------------------------------------------------------------------- */
void wlmaker_server_activate_task_list(wlmaker_server_t *server_ptr)
{
    server_ptr->task_list_enabled = true;
    wl_signal_emit(&server_ptr->task_list_enabled_event, NULL);
}

/* ------------------------------------------------------------------------- */
struct wlr_output *wlmaker_server_get_output_at_cursor(
    wlmaker_server_t *server_ptr)
{
    return wlr_output_layout_output_at(
        server_ptr->wlr_output_layout_ptr,
        wlmim_wlr_cursor(server_ptr->input_manager_ptr)->x,
        wlmim_wlr_cursor(server_ptr->input_manager_ptr)->y);
}

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

/* ------------------------------------------------------------------------- */
/** Handles unclaimed button events: Right 'down' opens root menu. */
void _wlmaker_server_unclaimed_button_event_handler(
    struct wl_listener *listener_ptr,
    void *data_ptr)
{
    wlmaker_server_t *server_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmaker_server_t, unclaimed_button_event_listener);
    wlmtk_button_event_t *button_event_ptr = data_ptr;

    if (BTN_RIGHT == button_event_ptr->button &&
        WLMTK_BUTTON_DOWN == button_event_ptr->type &&
        NULL != server_ptr->root_menu_ptr &&
        // TODO(kaeser@gubbe.ch): Clean up.
        !wlmtk_menu_is_open(
            wlmaker_root_menu_menu(server_ptr->root_menu_ptr))) {
        wlmtk_workspace_map_window(
            wlmtk_root_get_current_workspace(server_ptr->root_ptr),
            wlmaker_root_menu_window(server_ptr->root_menu_ptr));
        wlmtk_workspace_set_window_position(
            wlmtk_root_get_current_workspace(server_ptr->root_ptr),
            wlmaker_root_menu_window(server_ptr->root_menu_ptr),
            wlmim_wlr_cursor(server_ptr->input_manager_ptr)->x,
            wlmim_wlr_cursor(server_ptr->input_manager_ptr)->y);
        wlmtk_workspace_confine_within(
            wlmtk_root_get_current_workspace(server_ptr->root_ptr),
            wlmaker_root_menu_window(server_ptr->root_menu_ptr));
        wlmtk_menu_set_mode(
            wlmaker_root_menu_menu(server_ptr->root_menu_ptr),
            WLMTK_MENU_MODE_RIGHTCLICK);
        wlmtk_menu_set_open(
            wlmaker_root_menu_menu(server_ptr->root_menu_ptr),
            true);
    }
}

/* ------------------------------------------------------------------------- */
/** Handles input activity: Resets the idle timer. */
void _wlmaker_server_handle_input_activity(
    struct wl_listener *listener_ptr,
    __UNUSED__ void *data_ptr)
{
    wlmaker_server_t *server_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmaker_server_t, input_activity_listener);
     wlmaker_idle_monitor_reset(server_ptr->idle_monitor_ptr);
}

/* ------------------------------------------------------------------------- */
/** Handles suggestions to deactivate the task list. */
static void _wlmaker_server_handle_deactivate_task_list(
    struct wl_listener *listener_ptr,
    __UNUSED__ void *data_ptr)
{
    wlmaker_server_t *server_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmaker_server_t, deactivate_task_list_listener);

    if (!server_ptr->task_list_enabled) return;

    server_ptr->task_list_enabled = false;
    wl_signal_emit(&server_ptr->task_list_disabled_event, NULL);

    wlmtk_workspace_t *workspace_ptr =
        wlmtk_root_get_current_workspace(server_ptr->root_ptr);
    wlmtk_window_t *window_ptr =
        wlmtk_workspace_get_activated_window(workspace_ptr);
    if (NULL != window_ptr) {
        wlmtk_workspace_raise_window(workspace_ptr, window_ptr);
    }
}

/* ------------------------------------------------------------------------- */
/**
 * Approves a client's request to set the clipboard selection.
 *
 * @param listener_ptr
 * @param data_ptr            Points to a struct wlr_seat_request_set_selection_event.
 */
void handle_request_set_selection(
    struct wl_listener *listener_ptr,
    void *data_ptr)
{
    wlmaker_server_t *server_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmaker_server_t, request_set_selection_listener);
    struct wlr_seat_request_set_selection_event *event_ptr = data_ptr;

    wlr_seat_set_selection(
        server_ptr->wlr_seat_ptr, event_ptr->source, event_ptr->serial);
}

/* ------------------------------------------------------------------------- */
/**
 * Approves a client's request to set the primary selection.
 *
 * @param listener_ptr
 * @param data_ptr        Points to a struct
 *                        wlr_seat_request_set_primary_selection_event.
 */
void handle_request_set_primary_selection(
    struct wl_listener *listener_ptr,    void *data_ptr)
{
    wlmaker_server_t *server_ptr = BS_CONTAINER_OF(
        listener_ptr, wlmaker_server_t, request_set_primary_selection_listener);
    struct wlr_seat_request_set_primary_selection_event *event_ptr = data_ptr;

    wlr_seat_set_primary_selection(
        server_ptr->wlr_seat_ptr, event_ptr->source, event_ptr->serial);
}

/* == End of server.c ====================================================== */
