/*
 * Copyright (C) 2020 Igalia S.L.
 * All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#pragma once

#include <gtk/gtk.h>

#if USE(GTK4)

#define GDK_MOD1_MASK GDK_ALT_MASK

typedef GdkKeyEvent GdkEventKey;
typedef GdkFocusEvent GdkEventFocus;

static inline gboolean
gtk_widget_is_toplevel(GtkWidget* widget)
{
    // In theory anything which implements GtkRoot can be a toplevel widget,
    // in practice only the ones which are GtkWindow or derived need to be
    // considered here.
    return GTK_IS_WINDOW(widget);
}

static inline GtkWidget*
gtk_widget_get_toplevel(GtkWidget* widget)
{
    return GTK_WIDGET(gtk_widget_get_root(widget));
}

static inline void
gtk_widget_destroy(GtkWidget* widget)
{
    ASSERT(GTK_IS_WINDOW(widget));
    gtk_window_destroy(GTK_WINDOW(widget));
}

static inline void
gtk_window_get_position(GtkWindow*, int* x, int* y)
{
    *x = *y = 0;
}

static inline void
gtk_window_move(GtkWindow*, int, int)
{
}

static inline void
gtk_window_resize(GtkWindow* window, int width, int height)
{
    gtk_window_set_default_size(window, width, height);
}

static inline void
gtk_window_get_size(GtkWindow* window, int* width, int* height)
{
    gtk_window_get_default_size(window, width, height);
}

static inline void
gtk_init(int*, char***)
{
    gtk_init();
}

static inline gboolean
gtk_init_check(int*, char***)
{
    return gtk_init_check();
}

static inline GdkEvent*
gdk_event_copy(GdkEvent* event)
{
    return gdk_event_ref(event);
}

static inline GdkDevice*
gdk_event_get_source_device(const GdkEvent* event)
{
    return gdk_event_get_device(const_cast<GdkEvent*>(event));
}

static inline void
gtk_widget_size_allocate(GtkWidget* widget, GtkAllocation* allocation)
{
    gtk_widget_size_allocate(widget, allocation, -1);
}

static inline void
gtk_widget_queue_resize_no_redraw(GtkWidget* widget)
{
    gtk_widget_queue_resize(widget);
}

static inline void
gtk_entry_set_text(GtkEntry* entry, const char* text)
{
    gtk_editable_set_text(GTK_EDITABLE(entry), text);
}

static inline const char*
gtk_entry_get_text(GtkEntry* entry)
{
    return gtk_editable_get_text(GTK_EDITABLE(entry));
}

static inline void
gtk_label_set_line_wrap(GtkLabel* label, gboolean enable)
{
    gtk_label_set_wrap(label, enable);
}

static inline void
gtk_window_set_default(GtkWindow* window, GtkWidget* widget)
{
    gtk_window_set_default_widget(window, widget);
}

static inline gboolean
gdk_event_get_state(const GdkEvent *event, GdkModifierType *state)
{
    *state = gdk_event_get_modifier_state(const_cast<GdkEvent*>(event));
    // The GTK3 method returns TRUE if there is a state, otherwise
    // FALSE.
    return !!*state;
}

static inline gboolean
gdk_event_get_coords(const GdkEvent *event, double *x, double *y)
{
    return gdk_event_get_position(const_cast<GdkEvent*>(event), x, y);
}

static inline gboolean
gdk_event_get_root_coords(const GdkEvent *event, double *x, double *y)
{
    // GTK4 does not provide a way of obtaining screen-relative event coordinates, and even
    // on Wayland GTK3 cannot know where a surface is and will return the surface-relative
    // coordinates anyway, so do the same here.
    return gdk_event_get_position(const_cast<GdkEvent*>(event), x, y);
}

static inline gboolean
gdk_event_is_scroll_stop_event(const GdkEvent* event)
{
    return gdk_scroll_event_is_stop(const_cast<GdkEvent*>(event));
}

static inline gboolean
gdk_event_get_scroll_direction(const GdkEvent* event, GdkScrollDirection* direction)
{
    *direction = gdk_scroll_event_get_direction(const_cast<GdkEvent*>(event));
    // The GTK3 method returns TRUE if the scroll direction is not
    // GDK_SCROLL_SMOOTH, so do the same here.
    return *direction != GDK_SCROLL_SMOOTH;
}

static inline gboolean
gdk_event_get_scroll_deltas(const GdkEvent* event, gdouble *x, gdouble *y)
{
    gdk_scroll_event_get_deltas(const_cast<GdkEvent*>(event), x, y);
    // The GTK3 method returns TRUE if the event is a smooth scroll
    // event, so do the same here.
    return gdk_scroll_event_get_direction(const_cast<GdkEvent*>(event)) == GDK_SCROLL_SMOOTH;
}

static inline gboolean
gdk_event_get_button(const GdkEvent* event, guint* button)
{
    if (button)
        *button = gdk_button_event_get_button(const_cast<GdkEvent*>(event));
    return true;
}

static inline gboolean
gdk_event_get_keyval(const GdkEvent* event, guint* keyval)
{
    if (keyval)
        *keyval = gdk_key_event_get_keyval(const_cast<GdkEvent*>(event));
    return TRUE;
}

static inline gboolean
gdk_event_get_keycode(const GdkEvent* event, guint16* keycode)
{
    if (keycode)
        *keycode = gdk_key_event_get_keycode(const_cast<GdkEvent*>(event));
    return TRUE;
}

static inline int
gtk_native_dialog_run(GtkNativeDialog* dialog)
{
    struct RunDialogContext {
        GMainLoop *loop;
        int response;
    } context = { g_main_loop_new(nullptr, FALSE), 0 };

    gtk_native_dialog_show(dialog);
    g_signal_connect(dialog, "response", G_CALLBACK(+[](GtkNativeDialog*, int response, RunDialogContext* context) {
        context->response = response;
        g_main_loop_quit(context->loop);
    }), &context);
    g_main_loop_run(context.loop);
    g_main_loop_unref(context.loop);

    return context.response;
}

static inline int
gtk_dialog_run(GtkDialog* dialog)
{
    struct RunDialogContext {
        GMainLoop *loop;
        int response;
    } context = { g_main_loop_new(nullptr, FALSE), 0 };

    gtk_widget_show(GTK_WIDGET(dialog));
    g_signal_connect(dialog, "response", G_CALLBACK(+[](GtkDialog*, int response, RunDialogContext* context) {
        context->response = response;
        g_main_loop_quit(context->loop);
    }), &context);
    g_main_loop_run(context.loop);
    g_main_loop_unref(context.loop);

    return context.response;
}

static inline void
gtk_tree_view_column_cell_get_size(GtkTreeViewColumn* column, const GdkRectangle*, gint* xOffset, gint* yOffset, gint* width, gint* height)
{
    gtk_tree_view_column_cell_get_size(column, xOffset, yOffset, width, height);
}

#else // USE(GTK4)

static inline void
gtk_widget_add_css_class(GtkWidget* widget, const char* name)
{
    gtk_style_context_add_class(gtk_widget_get_style_context(widget), name);
}

static inline void
gtk_window_minimize(GtkWindow* window)
{
    gtk_window_iconify(window);
}

static inline void
gtk_window_unminimize(GtkWindow* window)
{
    gtk_window_deiconify(window);
}

static inline GtkWidget*
gtk_scrolled_window_new()
{
    return gtk_scrolled_window_new(nullptr, nullptr);
}

static inline GdkEvent*
gtk_event_controller_get_current_event(GtkEventController*)
{
    return gtk_get_current_event();
}

static inline GdkModifierType
gtk_event_controller_get_current_event_state(GtkEventController*)
{
    GdkModifierType modifiers;
    gtk_get_current_event_state(&modifiers);
    return modifiers;
}

static inline uint32_t
gtk_event_controller_get_current_event_time(GtkEventController*)
{
    return gtk_get_current_event_time();
}

static inline GtkIconTheme* gtk_icon_theme_get_for_display(GdkDisplay* display)
{
    return gtk_icon_theme_get_for_screen(gdk_display_get_default_screen(display));
}

static inline GdkDisplay*
gdk_event_get_display(GdkEvent* event)
{
    return event->any.window ? gdk_window_get_display(event->any.window) : gdk_display_get_default();
}

#endif // USE(GTK4)
