/*============================================================================
Copyright (c) 2015-2025 Raspberry Pi
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
============================================================================*/

/* NOTE raspi-config nonint functions obey sh return codes - 0 is in general success / yes / selected, 1 is failed / no / not selected */

#include <dirent.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <locale.h>
#include <glib/gstdio.h>

#include "rc_gui.h"

#include "system.h"
#include "display.h"
#include "interface.h"
#include "perform.h"
#include "local.h"

/*----------------------------------------------------------------------------*/
/* Typedefs and macros                                                        */
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/* Global data                                                                */
/*----------------------------------------------------------------------------*/

static GtkBuilder *builder;

GtkWidget *main_dlg, *msg_dlg;
GThread *pthread;
gboolean needs_reboot;
gboolean singledlg;
gboolean ispi;
wm_type wm;

char **batch = NULL;

#ifndef PLUGIN_NAME
static gulong draw_id;
#endif

/*----------------------------------------------------------------------------*/
/* Prototypes                                                                 */
/*----------------------------------------------------------------------------*/

static gboolean ok_clicked (GtkButton *button, gpointer data);
static void init_config (void);
#ifndef PLUGIN_NAME
static gboolean close_app (GtkButton *button, gpointer data);
static gboolean close_app_reboot (GtkButton *button, gpointer data);
static gboolean reboot_prompt (gpointer data);
static gpointer process_changes_thread (gpointer ptr);
static gboolean cancel_main (GtkButton *button, gpointer data);
static gboolean ok_main (GtkButton *button, gpointer data);
static gboolean close_prog (GtkWidget *widget, GdkEvent *event, gpointer data);
static gboolean init_window (gpointer data);
static gboolean draw (GtkWidget *wid, cairo_t *cr, gpointer data);
#endif

/*----------------------------------------------------------------------------*/
/* Function definitions                                                       */
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/* Helpers                                                                    */
/*----------------------------------------------------------------------------*/

int vsystem (const char *fmt, ...)
{
    char *cmdline;
    int res;

    va_list arg;
    va_start (arg, fmt);
    g_vasprintf (&cmdline, fmt, arg);
    va_end (arg);
    res = system (cmdline);
    g_free (cmdline);
    return res;
}

int get_status (char *cmd)
{
    int val, res = 0;
    char **line, *ptr;

    if (batch)
    {
        line = &batch[0];
        while (*line)
        {
            if (strstr (*line, cmd + 20))
            {
                ptr = g_strdup_printf ("%s %%d", cmd + 20);
                if (sscanf (*line, ptr, &val) == 1) res = val;
                g_free (ptr);
                return res;
            }
            line++;
        }
    }

    FILE *fp = popen (cmd, "r");
    char *buf = NULL;
    size_t siz = 0;

    if (fp == NULL) return 0;
    if (getline (&buf, &siz, fp) > 0)
    {
        if (sscanf (buf, "%d", &val) == 1) res = val;
    }
    pclose (fp);
    g_free (buf);
    return res;
}

char *get_string (char *cmd)
{
    char *line = NULL, *res = NULL;
    size_t len = 0;
    FILE *fp = popen (cmd, "r");

    if (fp == NULL) return NULL;
    if (getline (&line, &len, fp) > 0)
    {
        res = line;
        while (*res)
        {
            if (g_ascii_isspace (*res)) *res = 0;
            res++;
        }
        res = g_strdup (line);
    }
    pclose (fp);
    g_free (line);
    return res;
}

char *get_string_cached (char *cmd)
{
    if (batch)
    {
        char **ptr = &batch[0];
        while (*ptr)
        {
            if (strstr (*ptr, cmd + 20))
            {
                if (strchr (*ptr, ' ')) return g_strdup (strchr (*ptr, ' ') + 1);
                else return g_strdup ("");
            }
            ptr++;
        }
    }

    return get_string (cmd);
}

char *get_quoted_param (char *path, char *fname, char *toseek)
{
    char *pathname, *linebuf, *cptr, *dptr, *res;
    size_t len;

    pathname = g_strdup_printf ("%s/%s", path, fname);
    FILE *fp = fopen (pathname, "rb");
    g_free (pathname);
    if (!fp) return NULL;

    linebuf = NULL;
    len = 0;
    while (getline (&linebuf, &len, fp) > 0)
    {
        // skip whitespace at line start
        cptr = linebuf;
        while (*cptr == ' ' || *cptr == '\t') cptr++;

        // compare against string to find
        if (!strncmp_safe (cptr, toseek, strlen (toseek)))
        {
            // find string in quotes
            strtok (cptr, "\"");
            dptr = strtok (NULL, "\"\n\r");

            // copy to dest
            if (dptr) res = g_strdup (dptr);
            else res = NULL;

            // done
            g_free (linebuf);
            fclose (fp);
            return res;
        }
    }

    // end of file with no match
    g_free (linebuf);
    fclose (fp);
    return NULL;
}

void batch_get (int n, ...)
{
    char *cmdbuf, *tmp;
    const char *cmd;
    int i;
    FILE *fp;
    size_t siz;
    va_list args;

    cmdbuf = g_strdup ("raspi-config batch");

    // append the commands to batch
    va_start (args, n);
    for (i = 0; i < n; i++)
    {
        cmd = va_arg (args, const char *);
        tmp = g_strdup_printf ("%s '%s'", cmdbuf, cmd + 20);
        g_free (cmdbuf);
        cmdbuf = tmp;
    }
    va_end (args);

    // initialise the cache array
    batch = malloc (sizeof (char*));
    batch[0] = NULL;
    i = 0;

    // call the batch command
    fp = popen (cmdbuf, "r");
    while (1)
    {
        batch = realloc (batch, (i + 1) * sizeof (char *));
        batch[i] = NULL;
        siz = 0;
        if (getline (&batch[i], &siz, fp) == -1) break;
        i++;
    }
    batch[i] = NULL;
    pclose (fp);
}

void batch_free (void)
{
    if (batch)
    {
        char **ptr = &batch[0];
        while (*ptr)
        {
            g_free (*ptr);
            ptr++;
        }
        g_free (batch);
        batch = NULL;
    }
}

int strncmp_safe (const char *str1, const char *str2, size_t n)
{
    if (!str1) return -1;
    if (!str2) return 1;
    return strncmp (str1, str2, n);
}

/*----------------------------------------------------------------------------*/
/* Generic control handler                                                    */
/*----------------------------------------------------------------------------*/

#ifdef REALTIME

GtkWidget *cswitch;

static gboolean process_switch (gpointer data)
{
    char *setcmd = (char *) data;
    char *getcmd = strchr (setcmd, ';');
    *getcmd++ = 0;
    vsystem (setcmd);
    g_signal_handlers_block_matched (cswitch, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, on_switch, NULL);
    gtk_switch_set_active (GTK_SWITCH (cswitch), !get_status (getcmd));
    g_signal_handlers_unblock_matched (cswitch, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, on_switch, NULL);
    g_free (setcmd);
    clear_watch_cursor ();
    return FALSE;
}

void on_switch (GtkSwitch *btn, gpointer, const char *cmd)
{
    char *cmdline;
    cswitch = GTK_WIDGET (btn);
    set_watch_cursor ();
    cmdline = g_strdup_printf (cmd, (1 - gtk_switch_get_active (btn)));
    g_idle_add (process_switch, cmdline);
}

#endif

/*----------------------------------------------------------------------------*/
/* Message box                                                                */
/*----------------------------------------------------------------------------*/

void message (char *msg)
{
    GtkWidget *wid;
    GtkBuilder *builder;

    textdomain (GETTEXT_PACKAGE);
    builder = gtk_builder_new_from_file (PACKAGE_DATA_DIR "/ui/rc_gui.ui");

    msg_dlg = (GtkWidget *) gtk_builder_get_object (builder, "modal");
    if (main_dlg) gtk_window_set_transient_for (GTK_WINDOW (msg_dlg), GTK_WINDOW (main_dlg));

    wid = (GtkWidget *) gtk_builder_get_object (builder, "modal_msg");
    gtk_label_set_text (GTK_LABEL (wid), msg);

    gtk_widget_show (msg_dlg);

    g_object_unref (builder);
}

void info (char *msg)
{
    GtkWidget *wid;
    GtkBuilder *builder;

    textdomain (GETTEXT_PACKAGE);
    builder = gtk_builder_new_from_file (PACKAGE_DATA_DIR "/ui/rc_gui.ui");

    msg_dlg = (GtkWidget *) gtk_builder_get_object (builder, "modal");
    if (main_dlg) gtk_window_set_transient_for (GTK_WINDOW (msg_dlg), GTK_WINDOW (main_dlg));

    wid = (GtkWidget *) gtk_builder_get_object (builder, "modal_msg");
    gtk_label_set_text (GTK_LABEL (wid), msg);

    wid = (GtkWidget *) gtk_builder_get_object (builder, "modal_ok");
    g_signal_connect (wid, "clicked", G_CALLBACK (ok_clicked), NULL);
    gtk_widget_show (wid);

    wid = (GtkWidget *) gtk_builder_get_object (builder, "modal_buttons");
    gtk_widget_show (wid);

    gtk_widget_show (msg_dlg);

    g_object_unref (builder);
}

static gboolean ok_clicked (GtkButton *button, gpointer data)
{
    gtk_widget_destroy (msg_dlg);
    return FALSE;
}

/*----------------------------------------------------------------------------*/
/* Initial configuration                                                      */
/*----------------------------------------------------------------------------*/

static void init_config (void)
{
    DIR *dir = opendir ("/boot/firmware");

    if (dir)
    {
        batch_get (33, GET_PI_TYPE, GET_SPLASH, GET_ALOGIN_CLI, GET_ALOGIN_DESK, GET_PSUDO, GET_BOOT_CLI, GET_LEDS,
            GET_BROWSER, FF_INSTALLED, FFE_INSTALLED, CR_INSTALLED, GET_SSH, GET_VNC, GET_SPI, GET_I2C, GET_1WIRE,
            GET_SERIALCON, GET_SERIALHW, RVNC_INSTALLED, WVNC_INSTALLED, GET_BLANK, GET_OVERSCAN, GET_OVERSCAN2,
            GET_SQUEEK, GET_SQUEEKOUT, GET_VNC_RES, VKBD_INSTALLED, XSCR_INSTALLED, GET_USBI, GET_FAN, GET_FAN_GPIO,
            GET_FAN_TEMP, GET_OVERCLOCK);
        closedir (dir);
    }
    else
    {
        batch_get (20, GET_PI_TYPE, GET_IS_LIVE, GET_SPLASH, GET_ALOGIN_CLI, GET_ALOGIN_DESK, GET_PSUDO, GET_BOOT_CLI,
            GET_BROWSER, FF_INSTALLED, FFE_INSTALLED, CR_INSTALLED, GET_SSH, GET_VNC,
            RVNC_INSTALLED, WVNC_INSTALLED, GET_BLANK,
            GET_SQUEEK, GET_SQUEEKOUT, VKBD_INSTALLED, XSCR_INSTALLED);
    }

    load_system_tab (builder);
    load_display_tab (builder);
    load_interfacing_tab (builder);
    load_performance_tab (builder);
    load_localisation_tab (builder);

    if (get_status (GET_PI_TYPE) != -1) ispi = TRUE;
    else ispi = FALSE;

    batch_free ();
    needs_reboot = FALSE;
}

/*----------------------------------------------------------------------------*/
/* Plugin interface */
/*----------------------------------------------------------------------------*/

#ifdef PLUGIN_NAME

void init_plugin (GtkWidget *parent)
{
    setlocale (LC_ALL, "");
    bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
    textdomain (GETTEXT_PACKAGE);

    if (getenv ("WAYLAND_DISPLAY")) wm = WM_LABWC;
    else wm = WM_OPENBOX;

    singledlg = FALSE;

    main_dlg = parent;
    builder = gtk_builder_new_from_file (PACKAGE_DATA_DIR "/ui/rc_gui.ui");

    init_config ();
}

int plugin_tabs (void)
{
    if (ispi) return 5;
    else return 4;
}

const char *tab_name (int tab)
{
    switch (tab)
    {
        case 0 : return C_("tab", "System");
        case 1 : return C_("tab", "Display");
        case 2 : return C_("tab", "Interfaces");
        case 3 : return C_("tab", "Localisation");
        case 4 : return C_("tab", "Performance");
        default : return _("No such tab");
    }
}

const char *icon_name (int tab)
{
    switch (tab)
    {
        case 0 : return "applications-system";
        case 1 : return "computer";
        case 2 : return "rc-gui-interfaces";
        case 3 : return "rc-gui-localisation";
        case 4 : return "system-run";
        default : return NULL;
    }
}

const char *tab_id (int tab)
{
    switch (tab)
    {
        case 3 : return ("localisation");
        default : return NULL;
    }
}

GtkWidget *get_tab (int tab)
{
    GtkWidget *window, *plugin;

    window = (GtkWidget *) gtk_builder_get_object (builder, "notebook1");
    switch (tab)
    {
        case 0 :
            plugin = (GtkWidget *) gtk_builder_get_object (builder, "vbox10");
            break;
        case 1 :
            plugin = (GtkWidget *) gtk_builder_get_object (builder, "vbox50");
            break;
        case 2 :
            plugin = (GtkWidget *) gtk_builder_get_object (builder, "vbox20");
            break;
        case 3 :
            plugin = (GtkWidget *) gtk_builder_get_object (builder, "vbox40");
            break;
        case 4 :
            plugin = (GtkWidget *) gtk_builder_get_object (builder, "vbox30");
            break;
        default :
            plugin = NULL;
    }

    gtk_container_remove (GTK_CONTAINER (window), plugin);

    return plugin;
}

gboolean reboot_needed (void)
{
    if (needs_reboot) return TRUE;

    if (system_reboot ()) return TRUE;
    if (display_reboot ()) return TRUE;
    if (interfacing_reboot ()) return TRUE;
    if (performance_reboot ()) return TRUE;

    return FALSE;
}

void free_plugin (void)
{
    g_object_unref (builder);
}

#else

/*----------------------------------------------------------------------------*/
/* Reboot prompt                                                              */
/*----------------------------------------------------------------------------*/

static gboolean reboot_prompt (gpointer data)
{
    GtkWidget *wid;

    builder = gtk_builder_new_from_file (PACKAGE_DATA_DIR "/ui/rc_gui.ui");

    msg_dlg = (GtkWidget *) gtk_builder_get_object (builder, "modal");
    gtk_window_set_transient_for (GTK_WINDOW (msg_dlg), GTK_WINDOW (main_dlg));

    wid = (GtkWidget *) gtk_builder_get_object (builder, "modal_msg");
    gtk_label_set_text (GTK_LABEL (wid), _("The changes you have made require the Raspberry Pi to be rebooted to take effect.\n\nWould you like to reboot now? "));

    wid = (GtkWidget *) gtk_builder_get_object (builder, "modal_cancel");
    gtk_button_set_label (GTK_BUTTON (wid), _("_No"));
    g_signal_connect (wid, "clicked", G_CALLBACK (close_app), NULL);
    gtk_widget_show (wid);

    wid = (GtkWidget *) gtk_builder_get_object (builder, "modal_ok");
    gtk_button_set_label (GTK_BUTTON (wid), _("_Yes"));
    g_signal_connect (wid, "clicked", G_CALLBACK (close_app_reboot), NULL);
    gtk_widget_show (wid);

    wid = (GtkWidget *) gtk_builder_get_object (builder, "modal_buttons");
    gtk_widget_show (wid);

    gtk_widget_show (msg_dlg);

    g_object_unref (builder);
    return FALSE;
}

static gboolean close_app (GtkButton *button, gpointer data)
{
    gtk_widget_destroy (msg_dlg);
    gtk_main_quit ();
    return FALSE;
}

static gboolean close_app_reboot (GtkButton *button, gpointer data)
{
    gtk_widget_destroy (msg_dlg);
    gtk_main_quit ();
    vsystem ("reboot");
    return FALSE;
}

/*----------------------------------------------------------------------------*/
/* Main window button handlers                                                */
/*----------------------------------------------------------------------------*/

static gboolean ok_main (GtkButton *button, gpointer data)
{
    message (_("Updating configuration - please wait..."));
    pthread = g_thread_new (NULL, process_changes_thread, NULL);
    return FALSE;
}

static gpointer process_changes_thread (gpointer ptr)
{
    if (read_system_tab ()) needs_reboot = TRUE;
    if (read_display_tab ()) needs_reboot = TRUE;
    if (read_interfacing_tab ()) needs_reboot = TRUE;
    if (read_performance_tab ()) needs_reboot = TRUE;

    if (needs_reboot) g_idle_add (reboot_prompt, NULL);
    else gtk_main_quit ();

    return NULL;
}

static gboolean cancel_main (GtkButton *button, gpointer data)
{
    if (needs_reboot) reboot_prompt (NULL);
    else gtk_main_quit ();
    return FALSE;
}

static gboolean close_prog (GtkWidget *widget, GdkEvent *event, gpointer data)
{
    gtk_main_quit ();
    return TRUE;
}

/*----------------------------------------------------------------------------*/
/* Main window                                                                */
/*----------------------------------------------------------------------------*/

static gboolean init_window (gpointer data)
{
    init_config ();

    g_object_unref (builder);

    gtk_widget_show (main_dlg);
    gtk_widget_destroy (msg_dlg);

    return FALSE;
}

static gboolean draw (GtkWidget *wid, cairo_t *cr, gpointer data)
{
    g_signal_handler_disconnect (wid, draw_id);
    g_idle_add (init_window, NULL);
    return FALSE;
}

int main (int argc, char *argv[])
{
    GtkWidget *wid;

    setlocale (LC_ALL, "");
    bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
    textdomain (GETTEXT_PACKAGE);

    if (getenv ("WAYLAND_DISPLAY")) wm = WM_LABWC;
    else wm = WM_OPENBOX;

    main_dlg = NULL;
    gtk_init (&argc, &argv);

    // handle cases where single locale dialog is required
    singledlg = TRUE;
    if (argc == 2 && !g_strcmp0 (argv[1], "-w"))
    {
        on_set_wifi (NULL, NULL);
        return 0;
    }

    if (argc == 2 && !g_strcmp0 (argv[1], "-k"))
    {
        pthread = 0;
        on_set_keyboard (NULL, NULL);
        if (pthread) g_thread_join (pthread);
        return 0;
    }
    singledlg = FALSE;

    builder = gtk_builder_new_from_file (PACKAGE_DATA_DIR "/ui/rc_gui.ui");

    main_dlg = (GtkWidget *) gtk_builder_get_object (builder, "main_window");
    g_signal_connect (main_dlg, "delete_event", G_CALLBACK (close_prog), NULL);

    wid = (GtkWidget *) gtk_builder_get_object (builder, "button_ok");
    g_signal_connect (wid, "clicked", G_CALLBACK (ok_main), NULL);

    wid = (GtkWidget *) gtk_builder_get_object (builder, "button_cancel");
    g_signal_connect (wid, "clicked", G_CALLBACK (cancel_main), NULL);

    message (_("Loading configuration - please wait..."));
    draw_id = g_signal_connect (msg_dlg, "draw", G_CALLBACK (draw), NULL);

    gtk_main ();

    return 0;
}

#endif

/* End of file */
/*----------------------------------------------------------------------------*/
