#define _GNU_SOURCE 1

#include <config.h>

#include <errno.h>
#include <locale.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <gio/gio.h>
#include <gio/gdesktopappinfo.h>
#include <gio/gunixfdlist.h>

#include "xdg-desktop-portal-dbus.h"
#include "common-dbus.h"

#include "screenshot.h"
#include "request.h"
#include "utils.h"

static OrgCinnamonScreenshot *cinnamon;

typedef struct {
    XdpImplScreenshot *impl;
    GDBusMethodInvocation *invocation;
    Request *request;

    int response;
    char *uri;
    double red, green, blue;
    const char *retval;
    char *save_path;
} ScreenshotHandle;

static void
screenshot_handle_free (gpointer data)
{
    ScreenshotHandle *handle = data;

    g_clear_object (&handle->request);
    g_free (handle->uri);
    g_free (handle->save_path);

    g_free (handle);
}

static void
send_response (ScreenshotHandle *handle)
{
    GVariantBuilder opt_builder;

    if (handle->request->exported)
      request_unexport (handle->request);

    g_debug ("Sending screenshot/pickcolor response: %d", handle->response);

    g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);

    if (strcmp (handle->retval, "url") == 0)
    {
        if (handle->response == 0 && handle->uri != NULL)
            g_variant_builder_add (&opt_builder, "{sv}", "uri", g_variant_new_string (handle->uri));

        xdp_impl_screenshot_complete_screenshot (handle->impl,
                                                 handle->invocation,
                                                 handle->response,
                                                 g_variant_builder_end (&opt_builder));
    }
    else
    {
        if (handle->response == 0)
            g_variant_builder_add (&opt_builder, "{sv}", "color", g_variant_new ("(ddd)",
                                                                               handle->red,
                                                                               handle->green,
                                                                               handle->blue));

        xdp_impl_screenshot_complete_pick_color (handle->impl,
                                                 handle->invocation,
                                                 handle->response,
                                                 g_variant_builder_end (&opt_builder));
    }

    screenshot_handle_free (handle);
}

static void
xfce4_screenshooter_finished (GSubprocess  *proc,
                              GAsyncResult *res,
                              gpointer      user_data)
{
    ScreenshotHandle *handle = user_data;
    GError *error = NULL;

    if (!g_subprocess_wait_finish (proc, res, &error))
    {
        if (error != NULL)
        {
            g_warning ("Something went wrong with xfce4-screenshooter: (%d) %s", error->code, error->message);
            g_clear_error (&error);
        }

        handle->response = 2;
    }
    else if (!g_subprocess_get_successful (proc))
    {
        g_warning ("xfce4-screenshooter exited with failure");
        handle->response = 2;
    }
    else
    {
        handle->response = 0;
    }

    send_response (handle);
}

static void
cinnamon_color_pick_done (GObject *source,
                          GAsyncResult *result,
                          gpointer      data)
{
    ScreenshotHandle *handle = data;
    GError *error;
    GVariant *ret;

    error = NULL;

    if (!org_cinnamon_screenshot_call_pick_color_finish (cinnamon,
                                                         &ret,
                                                         result,
                                                         &error))
    {
        g_warning ("Failed to pick color: %s", error->message);
        g_clear_error (&error);
        handle->response = 2;
        send_response (handle);
        return;
    }

    if (!g_variant_lookup (ret, "color", "(ddd)",
                           &handle->red,
                           &handle->green,
                           &handle->blue))
    {
        g_warning ("PickColor didn't return a color");
        handle->response = 2;
        send_response (handle);
        return;
    }

    g_debug ("ColorPicker returned: %f %f %f", handle->red, handle->green, handle->blue);

    handle->response = 0;
    send_response (handle);
}

static void
cinnamon_screenshot_finished (GSubprocess  *proc,
                              GAsyncResult *res,
                              gpointer      user_data)
{
    ScreenshotHandle *handle = user_data;
    GError *error = NULL;

    if (!g_subprocess_wait_finish (proc, res, &error))
    {
        if (error != NULL)
        {
            g_warning ("Something went wrong with cinnamon-screenshot: (%d) %s", error->code, error->message);
            g_clear_error (&error);
        }
        handle->response = 2;
    }
    else if (!g_subprocess_get_successful (proc))
    {
        // cinnamon-screenshot exits non-zero when the user cancels the
        // interactive UI without saving — that's a request cancel rather
        // than an error, so report response = 1.
        g_debug ("cinnamon-screenshot exited non-zero (likely user cancel)");
        handle->response = 1;
    }
    else
    {
        handle->response = 0;
    }

    send_response (handle);
}

static gboolean
handle_close (XdpImplRequest *object,
              GDBusMethodInvocation *invocation,
              ScreenshotHandle *handle)
{
    GVariantBuilder opt_builder;

    g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
    xdp_impl_screenshot_complete_screenshot (handle->impl,
                                             handle->invocation,
                                             1,
                                             g_variant_builder_end (&opt_builder));
    return FALSE;
}

static gchar *
construct_filename (void)
{
    GDateTime *datetime;
    gchar *filename, *timestamp;
    const gchar *pic_dir;

    pic_dir = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
    if (pic_dir == NULL)
        pic_dir = g_get_home_dir ();

    datetime = g_date_time_new_now_local ();

    timestamp = g_date_time_format (datetime, "%Y-%m-%d %H-%M-%S");

    // Translators: this is the name of the file that the screenshot is
    // saved to. The placeholder is a timestamp, e.g. "2017-05-21 12-24-03".
    filename = g_strdup_printf ("%s/Screenshot from %s.png", pic_dir, timestamp);

    g_debug ("Saving screenshot to %s", filename);

    g_free (timestamp);
    g_date_time_unref (datetime);

    return filename;
}


static gboolean
handle_pick_color (XdpImplScreenshot *object,
                   GDBusMethodInvocation *invocation,
                   const char *arg_handle,
                   const char *arg_app_id,
                   const char *arg_parent_window,
                   GVariant *arg_options)
{
    g_autoptr(Request) request = NULL;
    const char *sender;
    ScreenshotHandle *handle;

    sender = g_dbus_method_invocation_get_sender (invocation);
    request = request_new (sender, arg_app_id, arg_handle);

    g_debug ("Got new PickColor request from '%s'", sender);

    handle = g_new0 (ScreenshotHandle, 1);
    handle->impl = object;
    handle->invocation = invocation;
    handle->request = g_object_ref (request);
    handle->retval = "color";
    handle->response = 2;

    g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle);
    request_export (request, g_dbus_method_invocation_get_connection (invocation));

    // Only cinnamon provides a color picker. xfce4-screenshooter has no
    // equivalent, so report failure rather than try to invoke a backend
    // that doesn't exist.
    if (!CINNAMON_MODE || cinnamon == NULL)
    {
        g_warning ("PickColor requested but no backend is available in this mode");
        handle->response = 2;
        send_response (handle);
        return TRUE;
    }

    org_cinnamon_screenshot_call_pick_color (cinnamon,
                                             NULL,
                                             cinnamon_color_pick_done,
                                             handle);

    return TRUE;
}


static gboolean
handle_screenshot (XdpImplScreenshot *object,
                   GDBusMethodInvocation *invocation,
                   const char *arg_handle,
                   const char *arg_app_id,
                   const char *arg_parent_window,
                   GVariant *arg_options)
{
    g_autoptr(Request) request = NULL;
    ScreenshotHandle *handle;
    const char *sender;

    sender = g_dbus_method_invocation_get_sender (invocation);

    g_debug ("Got new Screenshot request from '%s'", sender);

    request = request_new (sender, arg_app_id, arg_handle);

    handle = g_new0 (ScreenshotHandle, 1);
    handle->impl = object;
    handle->invocation = invocation;
    handle->request = g_object_ref (request);
    handle->retval = "url";
    handle->save_path = construct_filename ();
    handle->uri = g_filename_to_uri (handle->save_path, NULL, NULL);

    g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle);

    request_export (request, g_dbus_method_invocation_get_connection (invocation));

    if (XFCE_MODE)
    {
        if (g_find_program_in_path ("xfce4-screenshooter"))
        {
            GSubprocess *proc;
            GError *error = NULL;
            guint32 target = 0;
            const char *target_flag = NULL;

            g_variant_lookup (arg_options, "target", "u", &target);

            // xfce4-screenshooter targets: Screen via -f, Area via -r,
            // Active Window via -w. It has no interactive window picker,
            // so target=2 is not advertised; defensive fallback to -w if
            // a client asks anyway. We must always pass a target flag
            // because xfce4-screenshooter ignores --save unless one of
            // -f/-w/-r is given, so the "interactive" portal option (no
            // target specified) can't be honored here — default to
            // fullscreen.
            if (target == 4)
                target_flag = "-r";
            else if (target == 8 || target == 2)
                target_flag = "-w";
            else
                target_flag = "-f";

            g_debug ("Spawning xfce4-screenshooter (target=%u, flag=%s)",
                     target, target_flag);

            const gchar *argv[] = {
                "xfce4-screenshooter",
                target_flag,
                "--save", handle->save_path,
                NULL
            };

            proc = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_NONE, &error);

            if (error)
            {
                g_warning ("Could not take screenshot, call to xfce4-screenshooter failed: %s", error->message);
                g_clear_error (&error);
                handle->response = 2;
                send_response (handle);
            }
            else
            {
                g_subprocess_wait_async (proc, NULL, (GAsyncReadyCallback) xfce4_screenshooter_finished, handle);
            }
        }
        else
        {
            g_warning ("Screenshot requested but xfce4-screenshooter is not installed");
            handle->response = 2;
            send_response (handle);
        }
    }
    else
    if (CINNAMON_MODE)
    {
        if (g_find_program_in_path ("cinnamon-screenshot"))
        {
            GSubprocess *proc;
            GError *error = NULL;
            gboolean interactive = FALSE;
            guint32 target = 0;
            const char *target_flag = NULL;

            g_variant_lookup (arg_options, "interactive", "b", &interactive);
            g_variant_lookup (arg_options, "target", "u", &target);

            // Portal target values: 1=Screen, 2=Window, 4=Area, 8=Active Window.
            if (target == 2)
                target_flag = "--select-window";
            else if (target == 8)
                target_flag = "--window";
            else if (target == 4)
                target_flag = "--area";

            g_debug ("Spawning cinnamon-screenshot (interactive=%d, target=%u)",
                     interactive, target);

            GPtrArray *args = g_ptr_array_new ();
            g_ptr_array_add (args, (gpointer) "cinnamon-screenshot");
            g_ptr_array_add (args, (gpointer) "-f");
            g_ptr_array_add (args, handle->save_path);
            if (interactive)
                g_ptr_array_add (args, (gpointer) "-i");
            if (target_flag != NULL)
                g_ptr_array_add (args, (gpointer) target_flag);
            g_ptr_array_add (args, NULL);

            proc = g_subprocess_newv ((const gchar * const *) args->pdata,
                                      G_SUBPROCESS_FLAGS_NONE, &error);
            g_ptr_array_free (args, TRUE);

            if (error)
            {
                g_warning ("Could not take screenshot, call to cinnamon-screenshot failed: %s", error->message);
                g_clear_error (&error);
                handle->response = 2;
                send_response (handle);
            }
            else
            {
                g_subprocess_wait_async (proc, NULL, (GAsyncReadyCallback) cinnamon_screenshot_finished, handle);
            }
        }
        else
        {
            g_warning ("Screenshot requested but cinnamon-screenshot is not installed");
            handle->response = 2;
            send_response (handle);
        }
    }
    else
    {
        g_warning ("Screenshot not supported for current desktop mode");
        handle->response = 2;
        send_response (handle);
    }

    return TRUE;
}

gboolean
screenshot_init (GDBusConnection *bus,
                 GError **error)
{
    GDBusInterfaceSkeleton *helper;
    guint32 available_targets;

    helper = G_DBUS_INTERFACE_SKELETON (xdp_impl_screenshot_skeleton_new ());

    // Portal target bits: 1=Screen, 2=Window, 4=Area, 8=Active Window.
    // cinnamon-screenshot supports all four (target=2 runs the picker);
    // xfce4-screenshooter has no interactive window picker, so it omits
    // target=2.
    if (CINNAMON_MODE)
        available_targets = 1 | 2 | 4 | 8;
    else
        available_targets = 1 | 4 | 8;

    g_object_set (helper,
                  "version", 3,
                  "available-targets", available_targets,
                  NULL);

    g_signal_connect (helper, "handle-screenshot", G_CALLBACK (handle_screenshot), NULL);
    g_signal_connect (helper, "handle-pick-color", G_CALLBACK (handle_pick_color), NULL);

    if (!g_dbus_interface_skeleton_export (helper,
                                           bus,
                                           DESKTOP_PORTAL_OBJECT_PATH,
                                           error))
        return FALSE;

    // The cinnamon proxy is only used for PickColor — xfce4-screenshooter
    // has no equivalent, so skip creating it outside cinnamon mode.
    if (CINNAMON_MODE)
    {
        cinnamon = org_cinnamon_screenshot_proxy_new_sync (bus,
                                                           G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
                                                           "org.cinnamon.Screenshot",
                                                           "/org/cinnamon/Screenshot",
                                                           NULL,
                                                           error);
        if (cinnamon == NULL)
            return FALSE;
    }

    g_debug ("providing %s", g_dbus_interface_skeleton_get_info (helper)->name);

    return TRUE;
}
