#include "gtk-layer-demo.h"
#include <stdlib.h>

static GtkLayerShellLayer default_layer = GTK_LAYER_SHELL_LAYER_TOP;

static gboolean default_anchors[] = {FALSE, FALSE, FALSE, FALSE};
static int default_margins[] = {0, 0, 0, 0};

static gboolean default_auto_exclusive_zone = FALSE; // always set by command line option
static GtkLayerShellKeyboardMode default_keyboard_mode =
    GTK_LAYER_SHELL_KEYBOARD_MODE_NONE; // always set by command line option
static gboolean default_fixed_size = FALSE; // always set by command line option
static gboolean no_layer_shell = FALSE; // always set by command line option
static gboolean show_version_and_exit = FALSE; // always set by command line option

const char* prog_name = "gtk4-layer-demo";
const char* prog_summary = "A GTK application for demonstrating the functionality of the Layer Shell Wayland protocol";
const char* prog_details = "See https://github.com/wmww/gtk-layer-shell for more information, and to report bugs";

const char* anchor_edges_key = "anchor_edges";
const int fixed_size_width = 600;
const int fixed_size_height = 500;

gboolean layer_option_callback(const gchar* option_name, const gchar* value, void* data, GError **error);
gboolean anchor_option_callback(const gchar* option_name, const gchar* value, void* data, GError **error);
gboolean keyboard_option_callback(const gchar* option_name, const gchar* value, void* data, GError **error);
gboolean margin_option_callback(const gchar* option_name, const gchar* value, void* data, GError **error);

static const GOptionEntry options[] = { {
        .long_name = "version",
        .short_name = 'v',
        .flags = G_OPTION_FLAG_NONE,
        .arg = G_OPTION_ARG_NONE,
        .arg_data = &show_version_and_exit,
        .description = "Show version and exit",
        .arg_description = NULL,
    }, {
        .long_name = "layer",
        .short_name = 'l',
        .flags = G_OPTION_FLAG_NONE,
        .arg = G_OPTION_ARG_CALLBACK,
        .arg_data = (void*)&layer_option_callback,
        .description = "'overlay', 'top', 'bottom' or 'background' (or 'o', 't', 'b' or 'g')",
        .arg_description = NULL,
    }, {
        .long_name = "anchor",
        .short_name = 'a',
        .flags = G_OPTION_FLAG_OPTIONAL_ARG,
        .arg = G_OPTION_ARG_CALLBACK,
        .arg_data = (void*)&anchor_option_callback,
        .description = "A sequence of 'l', 'r', 't' and 'b' to anchor to those edges, or '0' for no anchor",
        .arg_description = NULL,
    }, {
        .long_name = "margin",
        .short_name = 'm',
        .flags = G_OPTION_FLAG_NONE,
        .arg = G_OPTION_ARG_CALLBACK,
        .arg_data = (void*)&margin_option_callback,
        .description = "Comma separated list of margin values, in the order LEFT,RIGHT,TOP,BOTTOM",
        .arg_description = NULL,
    }, {
        .long_name = "exclusive",
        .short_name = 'e',
        .flags = G_OPTION_FLAG_NONE,
        .arg = G_OPTION_ARG_NONE,
        .arg_data = &default_auto_exclusive_zone,
        .description = "Enable auto exclusive zone",
        .arg_description = NULL,
    }, {
        .long_name = "keyboard",
        .short_name = 'k',
        .flags = G_OPTION_FLAG_OPTIONAL_ARG,
        .arg = G_OPTION_ARG_CALLBACK,
        .arg_data = (void*)&keyboard_option_callback,
        .description = "Set keyboard interactivity: 'none', 'exclusive' or 'on-demand' (or 'n', 'e' or 'o')",
        .arg_description = NULL,
    }, {
        .long_name = "fixed-size",
        .short_name = 'f',
        .flags = G_OPTION_FLAG_NONE,
        .arg = G_OPTION_ARG_NONE,
        .arg_data = &default_fixed_size,
        .description = "Enable a fixed window size",
        .arg_description = NULL,
    }, {
        .long_name = "no-layer-shell",
        .short_name = 0,
        .flags = G_OPTION_FLAG_NONE,
        .arg = G_OPTION_ARG_NONE,
        .arg_data = &no_layer_shell,
        .description = "Disable gtk4-layer-shell, create a normal shell surface instead",
        .arg_description = NULL,
    }, { NULL, 0, 0, 0, NULL, NULL, NULL }
};

gboolean layer_option_callback(const gchar* _option_name, const gchar* value, void* _data, GError **error) {
    (void)_option_name;
    (void)_data;

    if (g_strcmp0(value, "overlay") == 0 || g_strcmp0(value, "o") == 0) {
        default_layer = GTK_LAYER_SHELL_LAYER_OVERLAY;
    } else if (g_strcmp0(value, "top") == 0 || g_strcmp0(value, "t") == 0) {
        default_layer = GTK_LAYER_SHELL_LAYER_TOP;
    } else if (g_strcmp0(value, "bottom") == 0 || g_strcmp0(value, "b") == 0) {
        default_layer = GTK_LAYER_SHELL_LAYER_BOTTOM;
    } else if (g_strcmp0(value, "background") == 0 || g_strcmp0(value, "g") == 0) {
        default_layer = GTK_LAYER_SHELL_LAYER_BACKGROUND;
    } else {
        g_set_error(error,
                     G_OPTION_ERROR,
                     G_OPTION_ERROR_FAILED,
                     "Invalid layer '%s' "
                     "(valid layers are 'overlay', 'top', 'bottom', 'background', 'o', 't', 'b' and 'g')",
                     value);
        return FALSE;
    }
    return TRUE;
}

gboolean anchor_option_callback(const gchar* _option_name, const gchar* value, void* _data, GError **error) {
    (void)_option_name;
    (void)_data;

    for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++) {
        default_anchors[i] = FALSE;
    }

    if (!value || !*value || g_strcmp0(value, "0") == 0 || g_strcmp0(value, "none") == 0) {
        return TRUE;
    }

    for (const char* c = value; *c; c++) {
        if (*c == 'l') {
            default_anchors[GTK_LAYER_SHELL_EDGE_LEFT] = TRUE;
        } else if (*c == 'r') {
            default_anchors[GTK_LAYER_SHELL_EDGE_RIGHT] = TRUE;
        } else if (*c == 't') {
            default_anchors[GTK_LAYER_SHELL_EDGE_TOP] = TRUE;
        } else if (*c == 'b') {
            default_anchors[GTK_LAYER_SHELL_EDGE_BOTTOM] = TRUE;
        } else {
            g_set_error(error,
                         G_OPTION_ERROR,
                         G_OPTION_ERROR_FAILED,
                         "Invalid anchor edge '%c' (valid edges are 'l', 'r', 't' and 'b')", *c);
            return FALSE;
        }
    }

    return TRUE;
}

gboolean keyboard_option_callback(const gchar* _option_name, const gchar* value, void* _data, GError **error) {
    (void)_option_name;
    (void)_data;

    if (!value) {
        // without argument = exclusive(retain old behavior)
        default_keyboard_mode = GTK_LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE;
        return TRUE;
    }
    if (g_strcmp0(value, "none") == 0 || g_strcmp0(value, "n") == 0) {
        default_keyboard_mode = GTK_LAYER_SHELL_KEYBOARD_MODE_NONE;
    } else if (g_strcmp0(value, "exclusive") == 0 || g_strcmp0(value, "e") == 0) {
        default_keyboard_mode = GTK_LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE;
    } else if (g_strcmp0(value, "on-demand") == 0 || g_strcmp0(value, "o") == 0) {
        default_keyboard_mode = GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND;
    } else {
        g_set_error(error,
                     G_OPTION_ERROR,
                     G_OPTION_ERROR_FAILED,
                     "Invalid keyboard interactivity '%s' "
                     "(valid values are 'none', 'exclusive', 'on-demand', 'n', 'e' and 'o')",
                     value);
        return FALSE;
    }
    return TRUE;
}

gboolean margin_option_callback(const gchar* _option_name, const gchar* value, void* _data, GError **error) {
    (void)_option_name;
    (void)_data;

    for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++) {
        if (!*value) {
            g_set_error(error,
                         G_OPTION_ERROR,
                         G_OPTION_ERROR_FAILED,
                         "Not enough comma separated arguments for margin");
            return FALSE;
        }
        char* end;
        long long margin = strtol(value, &end, 10);
        default_margins[i] = margin;
        if (end == value) {
            g_set_error(error,
                         G_OPTION_ERROR,
                         G_OPTION_ERROR_FAILED,
                         "Unable to parse margin");
            return FALSE;
        }
        value = end;
        if (*value == ',')
            value++;
    }
    if (*value) {
        g_set_error(error,
                     G_OPTION_ERROR,
                     G_OPTION_ERROR_FAILED,
                     "Too many comma separated arguments for margin");
        return FALSE;
    }
    return TRUE;
}

static void process_args(int* argc, char ***argv) {
    GOptionContext* context = g_option_context_new("");
    //g_option_context_add_group(context, gtk_get_option_group(TRUE));
    g_option_context_set_summary(context, prog_summary);
    g_option_context_set_description(context, prog_details);
    g_option_context_add_main_entries(context, options, NULL);

    GError* error = NULL;
    if (!g_option_context_parse(context, argc, argv, &error)) {
        g_printerr("%s\n", error->message);
        g_error_free(error);
        g_option_context_free(context);
        exit(1);
    }

    if (show_version_and_exit) {
        g_print(
            "%s on gtk4-layer-shell v%d.%d.%d\n",
            prog_name,
            gtk_layer_get_major_version(),
            gtk_layer_get_minor_version(),
            gtk_layer_get_micro_version());
        exit(0);
    }

    g_option_context_free(context);
}

void layer_window_update_orientation(GtkWindow* layer_window) {
    ToplevelData* data = g_object_get_data(G_OBJECT(layer_window), anchor_edges_key);
    gboolean horiz = data->edges[GTK_LAYER_SHELL_EDGE_LEFT] == data->edges[GTK_LAYER_SHELL_EDGE_RIGHT];
    gboolean vert = data->edges[GTK_LAYER_SHELL_EDGE_TOP] == data->edges[GTK_LAYER_SHELL_EDGE_BOTTOM];
    WindowOrientation orientation = WINDOW_ORIENTATION_NONE;
    if (horiz && (!vert || (data->edges[GTK_LAYER_SHELL_EDGE_LEFT] && !data->edges[GTK_LAYER_SHELL_EDGE_TOP]))) {
        orientation = WINDOW_ORIENTATION_HORIZONTAL;
    } else if (vert && (!horiz || (data->edges[GTK_LAYER_SHELL_EDGE_TOP] && !data->edges[GTK_LAYER_SHELL_EDGE_LEFT]))) {
        orientation = WINDOW_ORIENTATION_VERTICAL;
    }
    if (orientation != data->orientation) {
        data->orientation = orientation;
        g_signal_emit_by_name(layer_window, "orientation-changed", orientation);
    }
}

static void on_orientation_changed(GtkWindow* window, WindowOrientation orientation, ToplevelData* data) {
    (void)window;
    GtkOrientation orient_toplevel, orient_sub;
    orient_toplevel = GTK_ORIENTATION_HORIZONTAL;
    orient_sub = GTK_ORIENTATION_VERTICAL;

    switch (orientation) {
        case WINDOW_ORIENTATION_HORIZONTAL:
            orient_toplevel = GTK_ORIENTATION_HORIZONTAL;
            orient_sub = GTK_ORIENTATION_HORIZONTAL;
            break;
        case WINDOW_ORIENTATION_VERTICAL:
            orient_toplevel = GTK_ORIENTATION_VERTICAL;
            orient_sub = GTK_ORIENTATION_VERTICAL;
            break;
        case WINDOW_ORIENTATION_NONE:
            orient_toplevel = GTK_ORIENTATION_HORIZONTAL;
            orient_sub = GTK_ORIENTATION_VERTICAL;
            break;
    }
    gtk_orientable_set_orientation(GTK_ORIENTABLE(data->toplevel_box), orient_toplevel);
    gtk_orientable_set_orientation(GTK_ORIENTABLE(data->first_box), orient_sub);
    gtk_orientable_set_orientation(GTK_ORIENTABLE(data->second_box), orient_sub);
    //gtk_window_resize(window, 1, 1); // force the window to shrink to the smallest size it can
}

static void on_window_destroy(GtkWindow* window, GApplication* _data) {
    (void)_data;
    g_application_quit(G_APPLICATION(gtk_window_get_application(window)));
}

static void activate(GtkApplication* app, void* _data) {
    (void)_data;

    GtkWindow* gtk_window = GTK_WINDOW(gtk_application_window_new(app));

    ToplevelData* data = g_new0(ToplevelData, 1);
    g_object_set_data_full(G_OBJECT(gtk_window), anchor_edges_key, data, g_free);
    for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++)
        data->edges[i] = default_anchors[i];

    if (default_fixed_size)
        gtk_window_set_default_size(gtk_window, fixed_size_width, fixed_size_height);

    if (no_layer_shell) {
        g_message("GTK layer shell disabled on command line");
        g_message("Expect controls to have no effect and warnings to be shown");
    } else {
        gtk_layer_init_for_window(gtk_window);
    }

    for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++)
        gtk_layer_set_anchor(gtk_window, i, default_anchors[i]);
    for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++)
        gtk_layer_set_margin(gtk_window, i, default_margins[i]);
    gtk_layer_set_layer(gtk_window, default_layer);
    gtk_layer_set_keyboard_mode(gtk_window, default_keyboard_mode);
    gtk_layer_set_namespace(gtk_window, "demo");
    if (default_auto_exclusive_zone)
        gtk_layer_auto_exclusive_zone_enable(gtk_window);

    set_up_menubar(gtk_window);
    GtkWidget* centered_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_set_margin_bottom(centered_vbox, 10);
    gtk_widget_set_margin_top(centered_vbox, 10);
    gtk_widget_set_margin_start(centered_vbox, 10);
    gtk_widget_set_margin_end(centered_vbox, 10);
    gtk_window_set_child(gtk_window, centered_vbox);
    GtkWidget* centered_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_append(GTK_BOX(centered_vbox), centered_hbox);
    data->toplevel_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
    //gtk_container_set_border_width(GTK_CONTAINER(data->toplevel_box), 16);
    gtk_box_append(GTK_BOX(centered_hbox), data->toplevel_box); {
        data->first_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
        gtk_widget_set_vexpand(data->first_box, TRUE);
        gtk_box_append(GTK_BOX(data->toplevel_box), data->first_box); {
            GtkWidget* selections_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
            gtk_widget_set_vexpand(selections_box, TRUE);
            gtk_box_append(GTK_BOX(data->first_box), selections_box);
            gtk_box_append(GTK_BOX(selections_box),
                            monitor_selection_new(gtk_window));
            gtk_box_append(GTK_BOX(selections_box),
                            layer_selection_new(gtk_window, default_layer));
        }{
            gtk_box_append(GTK_BOX(data->first_box),
                            anchor_control_new(gtk_window, default_anchors));
        }
    }{
        data->second_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
        gtk_box_append(GTK_BOX(data->toplevel_box), data->second_box); {
            GtkWidget* toggles_box = mscl_toggles_new(gtk_window,
                                                       default_auto_exclusive_zone,
                                                       default_fixed_size);
            gtk_box_append(GTK_BOX(data->second_box), toggles_box);
            GtkWidget* kb_box = keyboard_selection_new(gtk_window, default_keyboard_mode);
            gtk_box_append(GTK_BOX(data->second_box), kb_box);
        } {
            GtkWidget* margin_and_version_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
            gtk_box_append(GTK_BOX(margin_and_version_box),
                            margin_control_new(gtk_window, default_margins));
            gtk_box_append(GTK_BOX(margin_and_version_box),
                            version_info_new());
            gtk_box_append(GTK_BOX(data->second_box),
                            margin_and_version_box);
        }
    }

    g_signal_connect(gtk_window, "orientation-changed", G_CALLBACK(on_orientation_changed), data);
    data->orientation = -1; // invalid value will force anchor_edges_update_orientation to update
    layer_window_update_orientation(gtk_window);

    g_signal_connect(gtk_window, "destroy", G_CALLBACK(on_window_destroy), NULL);
    gtk_window_present(gtk_window);
}

int main(int argc, char **argv) {
    g_set_prgname(prog_name);
    process_args(&argc, &argv);

    // The int arg is an enum of type WindowOrientation
    // Signal is emitted in anchor-control.c
    g_signal_new("orientation-changed",
                  GTK_TYPE_WINDOW,
                  G_SIGNAL_RUN_FIRST,
                  0, NULL, NULL,
                  g_cclosure_marshal_VOID__INT,
                  G_TYPE_NONE, 1, G_TYPE_INT);

    GtkApplication* app = gtk_application_new("com.github.wmww.gtk4-layer-shell.demo", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return status;
}
