/*
 * Copyright © 2011 Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: Peter Hutterer <peter.hutterer@redhat.com>
 *          Bastien Nocera <hadess@hadess.net>
 *
 */

#include <config.h>

#include "cc-wacom-panel.h"
#include "cc-wacom-stylus-action-dialog.h"
#include "cc-wacom-stylus-page.h"
#include "panels/common/cc-list-row.h"
#include "panels/common/cc-mask-paintable.h"
#include <adwaita.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <shell/cc-panel.h>

#include <string.h>

struct _CcWacomStylusPage {
    GtkBox parent_instance;

    CcWacomPanel *panel;
    GtkWidget *stylus_section;
    CcMaskPaintable *stylus_paintable;
    GtkWidget *stylus_button1_action_row;
    GtkWidget *stylus_button2_action_row;
    GtkWidget *stylus_button3_action_row;
    GtkWidget *stylus_eraser_pressure;
    GtkWidget *stylus_tip_pressure_scale;
    GtkWidget *stylus_eraser_pressure_scale;
    GtkAdjustment *stylus_tip_pressure_adjustment;
    GtkAdjustment *stylus_eraser_pressure_adjustment;
    CcWacomTool *stylus;
    GSettings *stylus_settings;

    gboolean highlighted;
};

G_DEFINE_FINAL_TYPE (CcWacomStylusPage, cc_wacom_stylus_page, GTK_TYPE_BOX)

static void
map_pressurecurve (double val, graphene_point_t *p1, graphene_point_t *p2)
{
    g_return_if_fail (val >= 0.0 && val <= 100.0);

    /* The second point's x/y axis is an inverse of the first point's y/x axis,
     * so that:
     *    0% maps to 0/100  0/100
     *   10% maps to 0/80  20/100
     *   50% maps to 0/0   100/100
     *   60% maps to 20/0  100/80
     *  100% maps to 100/0 100/0
     */

    p1->x = -100 + val * 2;
    p1->y = 100 - val * 2;
    p1->x = CLAMP (p1->x, 0, 100);
    p1->y = CLAMP (p1->y, 0, 100);

    p2->x = 100 - p1->y;
    p2->y = 100 - p1->x;
}

static void
set_pressurecurve (GtkRange *range, GSettings *settings, const gchar *key)
{
    gint slider_val = gtk_range_get_value (range) / 2;
    graphene_point_t p1, p2;
    GVariantBuilder builder;

    g_return_if_fail (slider_val >= 0 && slider_val <= 100);

    map_pressurecurve (slider_val, &p1, &p2);

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("ai"));
    g_variant_builder_add (&builder, "i", (int) p1.x);
    g_variant_builder_add (&builder, "i", (int) p1.y);
    g_variant_builder_add (&builder, "i", (int) p2.x);
    g_variant_builder_add (&builder, "i", (int) p2.y);

    g_settings_set_value (settings, key, g_variant_builder_end (&builder));
}

static void
on_tip_pressure_value_changed (CcWacomStylusPage *page)
{
    set_pressurecurve (GTK_RANGE (page->stylus_tip_pressure_scale), page->stylus_settings, "pressure-curve");
}

static void
on_eraser_pressure_value_changed (CcWacomStylusPage *page)
{
    set_pressurecurve (GTK_RANGE (page->stylus_eraser_pressure_scale), page->stylus_settings, "eraser-pressure-curve");
}

static void
set_feel_from_gsettings (GtkAdjustment *adjustment, GSettings *settings, const gchar *key)
{
    GVariant *variant;
    const gint32 *values;
    gsize nvalues;
    int i;
    graphene_point_t p1, p2;

    variant = g_settings_get_value (settings, key);
    values = g_variant_get_fixed_array (variant, &nvalues, sizeof (gint32));

    if (nvalues != 4) {
        g_warning ("Invalid pressure curve format, expected 4 values (got %" G_GSIZE_FORMAT ")", nvalues);
        return;
    }

    p1 = GRAPHENE_POINT_INIT (values[0], values[1]);
    p2 = GRAPHENE_POINT_INIT (values[2], values[3]);

    /* Our functions in set_pressurecurve() are lossy thanks to CLAMP so
     * we calculate a (possibly wrong) slider value from our points, compare
     * what points that value would produce and if they match - hooray!
     */
    for (i = 0; i < 4; i++) {
        double val = -1;

        switch (i) {
        case 0:
            val = (p1.x + 100) / 2.0;
            break;
        case 1:
            val = (-p1.y + 100) / 2.0;
            break;
        case 2:
            val = p2.x / 2.0;
            break;
        case 3:
            val = (-p2.y + 200) / 2.0;
            break;
        }

        if (val >= 0.0 && val <= 100.0) {
            graphene_point_t mapped_p1, mapped_p2;

            map_pressurecurve (val, &mapped_p1, &mapped_p2);
            if (graphene_point_equal (&p1, &mapped_p1) && graphene_point_equal (&p2, &mapped_p2)) {
                gtk_adjustment_set_value (adjustment, (int) (val * 2));
                break;
            }
        }
    }
}

static void
cc_wacom_stylus_page_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
    switch (property_id) {
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
cc_wacom_stylus_page_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
    switch (property_id) {
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
present_action_dialog (CcWacomStylusPage *page, guint button, const char *key, const char *keybinding_key)
{
    GtkWidget *action_dialog;

    action_dialog = cc_wacom_stylus_action_dialog_new (page->stylus_settings, cc_wacom_tool_get_name (page->stylus),
                                                       button, key, keybinding_key);

    adw_dialog_present (ADW_DIALOG (action_dialog), GTK_WIDGET (page));
}

static void
on_stylus_button1_action_activated (CcWacomStylusPage *page)
{
    present_action_dialog (page, 1, "button-action", "button-keybinding");
}

static void
on_stylus_button2_action_activated (CcWacomStylusPage *page)
{
    present_action_dialog (page, 2, "secondary-button-action", "secondary-button-keybinding");
}

static void
on_stylus_button3_action_activated (CcWacomStylusPage *page)
{
    present_action_dialog (page, 3, "tertiary-button-action", "tertiary-button-keybinding");
}

static void
update_mask_color (CcWacomStylusPage *page)
{
    AdwStyleManager *style_manager = adw_style_manager_get_default ();
    GdkRGBA rgba;

    if (page->highlighted) {
        AdwAccentColor color = adw_style_manager_get_accent_color (style_manager);

        adw_accent_color_to_rgba (color, &rgba);
    } else {
        gtk_widget_get_color (GTK_WIDGET (page), &rgba);

        if (adw_style_manager_get_high_contrast (style_manager))
            rgba.alpha *= 0.5;
        else
            rgba.alpha *= 0.2;
    }

    cc_mask_paintable_set_rgba (page->stylus_paintable, &rgba);
}

static void
cc_wacom_stylus_page_css_changed (GtkWidget *widget, GtkCssStyleChange *change)
{
    CcWacomStylusPage *page = CC_WACOM_STYLUS_PAGE (widget);

    GTK_WIDGET_CLASS (cc_wacom_stylus_page_parent_class)->css_changed (widget, change);

    update_mask_color (page);
}

static void
cc_wacom_stylus_page_class_init (CcWacomStylusPageClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

    object_class->get_property = cc_wacom_stylus_page_get_property;
    object_class->set_property = cc_wacom_stylus_page_set_property;

    widget_class->css_changed = cc_wacom_stylus_page_css_changed;

    g_type_ensure (CC_TYPE_LIST_ROW);
    g_type_ensure (CC_TYPE_MASK_PAINTABLE);

    gtk_widget_class_set_template_from_resource (widget_class,
                                                 "/org/gnome/control-center/wacom/cc-wacom-stylus-page.ui");

    gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_section);
    gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_paintable);
    gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_button1_action_row);
    gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_button2_action_row);
    gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_button3_action_row);
    gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_eraser_pressure);
    gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_tip_pressure_scale);
    gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_eraser_pressure_scale);
    gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_tip_pressure_adjustment);
    gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_eraser_pressure_adjustment);

    gtk_widget_class_bind_template_callback (widget_class, on_stylus_button1_action_activated);
    gtk_widget_class_bind_template_callback (widget_class, on_stylus_button2_action_activated);
    gtk_widget_class_bind_template_callback (widget_class, on_stylus_button3_action_activated);
    gtk_widget_class_bind_template_callback (widget_class, on_tip_pressure_value_changed);
    gtk_widget_class_bind_template_callback (widget_class, on_eraser_pressure_value_changed);
}

static void
cc_wacom_stylus_page_init (CcWacomStylusPage *page)
{
    gtk_widget_init_template (GTK_WIDGET (page));
}

static void
set_icon_name (CcWacomStylusPage *page, const char *icon_name)
{
    g_autofree gchar *resource = NULL;

    resource = g_strdup_printf ("/org/gnome/control-center/wacom/%s.svg", icon_name);

    cc_mask_paintable_set_resource_scaled (page->stylus_paintable, resource, GTK_WIDGET (page));
}

static void
on_button_action_changed (GSettings *settings, gchar *key, gpointer user_data)
{
    GDesktopStylusButtonAction action = g_settings_get_enum (settings, key);
    const char *text = cc_wacom_panel_get_stylus_button_action_label (action);

    if (text)
        cc_list_row_set_secondary_label (CC_LIST_ROW (user_data), text);
}

GtkWidget *
cc_wacom_stylus_page_new (CcWacomPanel *panel, CcWacomTool *stylus)
{
    CcWacomStylusPage *page;
    guint num_buttons;
    gboolean has_paired_eraser;
    g_autofree char *tool_description = NULL;

    g_return_val_if_fail (CC_IS_WACOM_TOOL (stylus), NULL);

    page = g_object_new (CC_TYPE_WACOM_STYLUS_PAGE, NULL);

    page->panel = panel;
    page->stylus = stylus;

    /* Stylus name */
    adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (page->stylus_section), cc_wacom_tool_get_name (stylus));
    tool_description = cc_wacom_tool_get_description (stylus);
    adw_preferences_group_set_description (ADW_PREFERENCES_GROUP (page->stylus_section), tool_description);

    /* Icon */
    set_icon_name (page, cc_wacom_tool_get_icon_name (stylus));

    /* Settings */
    page->stylus_settings = cc_wacom_tool_get_settings (stylus);
    has_paired_eraser = cc_wacom_tool_get_has_paired_eraser (stylus);

    num_buttons = cc_wacom_tool_get_num_buttons (stylus);
    gtk_widget_set_visible (page->stylus_button3_action_row, num_buttons >= 3);
    gtk_widget_set_visible (page->stylus_button2_action_row, num_buttons >= 2);
    gtk_widget_set_visible (page->stylus_button1_action_row, num_buttons >= 1);
    gtk_widget_set_visible (page->stylus_eraser_pressure, has_paired_eraser);

    set_feel_from_gsettings (page->stylus_tip_pressure_adjustment, page->stylus_settings, "pressure-curve");
    set_feel_from_gsettings (page->stylus_eraser_pressure_adjustment, page->stylus_settings, "eraser-pressure-curve");

    g_signal_connect (G_OBJECT (page->stylus_settings), "changed::button-action", G_CALLBACK (on_button_action_changed),
                      page->stylus_button1_action_row);
    g_signal_connect (G_OBJECT (page->stylus_settings), "changed::secondary-button-action",
                      G_CALLBACK (on_button_action_changed), page->stylus_button2_action_row);
    g_signal_connect (G_OBJECT (page->stylus_settings), "changed::tertiary-button-action",
                      G_CALLBACK (on_button_action_changed), page->stylus_button3_action_row);

    on_button_action_changed (page->stylus_settings, "button-action", page->stylus_button1_action_row);
    on_button_action_changed (page->stylus_settings, "secondary-button-action", page->stylus_button2_action_row);
    on_button_action_changed (page->stylus_settings, "tertiary-button-action", page->stylus_button3_action_row);

    return GTK_WIDGET (page);
}

CcWacomTool *
cc_wacom_stylus_page_get_tool (CcWacomStylusPage *page)
{
    return page->stylus;
}

void
cc_wacom_stylus_page_set_highlight (CcWacomStylusPage *page, gboolean highlight)
{
    if (page->highlighted != highlight) {
        page->highlighted = highlight;
        update_mask_color (page);
    }
}

const char *
cc_wacom_panel_get_stylus_button_action_label (GDesktopStylusButtonAction action)
{
    const char *text = NULL;

    switch (action) {
    case G_DESKTOP_STYLUS_BUTTON_ACTION_DEFAULT:
        text = _("Left Mousebutton Click");
        break;
    case G_DESKTOP_STYLUS_BUTTON_ACTION_MIDDLE:
        text = _("Middle Mousebutton Click");
        break;
    case G_DESKTOP_STYLUS_BUTTON_ACTION_RIGHT:
        text = _("Right Mousebutton Click");
        break;
    case G_DESKTOP_STYLUS_BUTTON_ACTION_BACK:
        /* Translators: this is the "go back" action of a button  */
        text = _("Back");
        break;
    case G_DESKTOP_STYLUS_BUTTON_ACTION_FORWARD:
        /* Translators: this is the "go forward" action of a button  */
        text = _("Forward");
        break;
    case G_DESKTOP_STYLUS_BUTTON_ACTION_KEYBINDING:
        text = _("Assign Keystroke");
        break;
    case G_DESKTOP_STYLUS_BUTTON_ACTION_SWITCH_MONITOR:
        text = _("Switch Monitor");
        break;
    }

    return text;
}
