/*
 * Copyright © 2016 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/>.
 *
 * Author: Carlos Garnacho <carlosg@gnome.org>
 */

#include "config.h"
#include <cairo/cairo.h>
#include "cc-drawing-area.h"

typedef struct _CcDrawingArea CcDrawingArea;

struct _CcDrawingArea {
	GtkDrawingArea parent;
	GtkGesture *stylus_gesture;
	cairo_surface_t *surface;
	cairo_t *cr;
};

G_DEFINE_TYPE (CcDrawingArea, cc_drawing_area, GTK_TYPE_DRAWING_AREA)

static void
ensure_drawing_surface (CcDrawingArea *area,
			gint           width,
			gint           height)
{
	if (!area->surface ||
	    cairo_image_surface_get_width (area->surface) != width ||
	    cairo_image_surface_get_height (area->surface) != height) {
		cairo_surface_t *surface;

		surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
						      width, height);
		if (area->surface) {
			cairo_t *cr;

			cr = cairo_create (surface);
			cairo_set_source_surface (cr, area->surface, 0, 0);
			cairo_paint (cr);

			cairo_surface_destroy (area->surface);
			cairo_destroy (area->cr);
			cairo_destroy (cr);
		}

		area->surface = surface;
		area->cr = cairo_create (surface);
	}
}

static void
cc_drawing_area_map (GtkWidget *widget)
{
	GtkAllocation allocation;

	GTK_WIDGET_CLASS (cc_drawing_area_parent_class)->map (widget);

	gtk_widget_get_allocation (widget, &allocation);
	ensure_drawing_surface (CC_DRAWING_AREA (widget),
				allocation.width, allocation.height);
}

static void
cc_drawing_area_unmap (GtkWidget *widget)
{
	CcDrawingArea *area = CC_DRAWING_AREA (widget);

	if (area->cr) {
		cairo_destroy (area->cr);
		area->cr = NULL;
	}

	if (area->surface) {
		cairo_surface_destroy (area->surface);
		area->surface = NULL;
	}

	GTK_WIDGET_CLASS (cc_drawing_area_parent_class)->unmap (widget);
}

static void
draw_cb (GtkDrawingArea *drawing_area,
         cairo_t   *cr,
         gint       width,
         gint       height,
         gpointer   user_data)
{
	CcDrawingArea *area = CC_DRAWING_AREA (drawing_area);

	ensure_drawing_surface (area, width, height);

	cairo_set_source_rgb (cr, 1, 1, 1);
	cairo_paint (cr);

	cairo_set_source_surface (cr, area->surface, 0, 0);
	cairo_paint (cr);

	cairo_set_source_rgb (cr, 0.6, 0.6, 0.6);
	cairo_rectangle (cr, 0, 0, width, height);
	cairo_stroke (cr);
}

static void
stylus_down_cb (CcDrawingArea    *area,
		double            x,
		double            y)
{
	cairo_new_path (area->cr);
}

static void
apply_stroke (CcDrawingArea *area,
	      GdkDeviceTool *tool,
	      double         x,
	      double         y,
	      double         pressure)
{
	if (gdk_device_tool_get_tool_type (tool) == GDK_DEVICE_TOOL_TYPE_ERASER) {
		cairo_set_line_width (area->cr, 10 * pressure);
		cairo_set_operator (area->cr, CAIRO_OPERATOR_DEST_OUT);
	} else {
		cairo_set_line_width (area->cr, 4 * pressure);
		cairo_set_operator (area->cr, CAIRO_OPERATOR_SATURATE);
	}

	cairo_set_source_rgba (area->cr, 0, 0, 0, pressure);
	cairo_line_to (area->cr, x, y);
	cairo_stroke (area->cr);

	cairo_move_to (area->cr, x, y);
}

static void
stylus_motion_cb (CcDrawingArea    *area,
		  double            x,
		  double            y,
		  GtkGestureStylus *gesture)
{
	g_autofree GdkTimeCoord *backlog = NULL;
	GdkDeviceTool *tool;
	gdouble pressure;
	guint i, n_items;

	tool = gtk_gesture_stylus_get_device_tool (gesture);

	if (gtk_gesture_stylus_get_backlog (gesture, &backlog, &n_items)) {
		for (i = 0; i < n_items; i++) {
			apply_stroke (area, tool,
				      backlog[i].axes[GDK_AXIS_X],
				      backlog[i].axes[GDK_AXIS_Y],
				      backlog[i].axes[GDK_AXIS_PRESSURE]);
		}
	}

	gtk_gesture_stylus_get_axis (gesture,
				     GDK_AXIS_PRESSURE,
				     &pressure);

	apply_stroke (area, tool, x, y, pressure);

	gtk_widget_queue_draw (GTK_WIDGET (area));
}

static void
cc_drawing_area_class_init (CcDrawingAreaClass *klass)
{
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

	widget_class->map = cc_drawing_area_map;
	widget_class->unmap = cc_drawing_area_unmap;
}

static void
cc_drawing_area_init (CcDrawingArea *area)
{
	gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area), draw_cb, NULL, NULL);
	area->stylus_gesture = gtk_gesture_stylus_new ();
	g_signal_connect_swapped (area->stylus_gesture, "down",
                                  G_CALLBACK (stylus_down_cb), area);
	g_signal_connect_swapped (area->stylus_gesture, "motion",
                                  G_CALLBACK (stylus_motion_cb), area);
	gtk_widget_add_controller (GTK_WIDGET (area),
				   GTK_EVENT_CONTROLLER (area->stylus_gesture));
}

GtkWidget *
cc_drawing_area_new (void)
{
	return g_object_new (CC_TYPE_DRAWING_AREA, NULL);
}
