/*
 * Copyright (c) 2014 Benjamin Otte <ottte@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "cellrenderergraph.h"

#include "graphdata.h"

#include "gtksnapshot.h"
#include "gtkstylecontext.h"

enum {
  PROP_0,
  PROP_DATA,
  PROP_MINIMUM,
  PROP_MAXIMUM
};

struct _GtkCellRendererGraphPrivate
{
  GtkGraphData *data;
  double minimum;
  double maximum;
};

G_DEFINE_TYPE_WITH_PRIVATE (GtkCellRendererGraph, gtk_cell_renderer_graph, GTK_TYPE_CELL_RENDERER)

static void 
gtk_cell_renderer_graph_dispose (GObject *object)
{
  GtkCellRendererGraph *graph = GTK_CELL_RENDERER_GRAPH (object);
  GtkCellRendererGraphPrivate *priv = graph->priv;

  g_clear_object (&priv->data);

  G_OBJECT_CLASS (gtk_cell_renderer_graph_parent_class)->dispose (object);
}

static void
gtk_cell_renderer_graph_get_property (GObject    *object,
                                      guint       param_id,
                                      GValue     *value,
                                      GParamSpec *pspec)
{
  GtkCellRendererGraph *cell = GTK_CELL_RENDERER_GRAPH (object);
  GtkCellRendererGraphPrivate *priv = cell->priv;

  switch (param_id)
    {
      case PROP_DATA:
        g_value_set_object (value, priv->data);
        break;
      case PROP_MINIMUM:
        g_value_set_double (value, priv->minimum);
        break;
      case PROP_MAXIMUM:
        g_value_set_double (value, priv->maximum);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
    }
}

static void
gtk_cell_renderer_graph_set_property (GObject      *object,
                                      guint         param_id,
                                      const GValue *value,
                                      GParamSpec   *pspec)
{
  GtkCellRendererGraph *cell = GTK_CELL_RENDERER_GRAPH (object);
  GtkCellRendererGraphPrivate *priv = cell->priv;

  switch (param_id)
    {
      case PROP_DATA:
        if (priv->data != g_value_get_object (value))
          {
            if (priv->data)
              g_object_unref (priv->data);
            priv->data = g_value_dup_object (value);
            g_object_notify_by_pspec (object, pspec);
          }
        break;
      case PROP_MINIMUM:
        if (priv->minimum != g_value_get_double (value))
          {
            priv->minimum = g_value_get_double (value);
            g_object_notify_by_pspec (object, pspec);
          }
        break;
      case PROP_MAXIMUM:
        if (priv->maximum != g_value_get_double (value))
          {
            priv->maximum = g_value_get_double (value);
            g_object_notify_by_pspec (object, pspec);
          }
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
    }
}

#define MIN_HEIGHT 24
#define MIN_WIDTH (3 * MIN_HEIGHT)

static void
gtk_cell_renderer_graph_get_preferred_width (GtkCellRenderer *cell,
                                             GtkWidget       *widget,
                                             int             *minimum,
                                             int             *natural)
{
  int xpad, size;

  g_object_get (cell, "xpad", &xpad, NULL);

  size = MIN_WIDTH + 2 * xpad;

  if (minimum != NULL)
    *minimum = size;
  if (natural != NULL)
    *natural = size;
}

static void
gtk_cell_renderer_graph_get_preferred_height (GtkCellRenderer *cell,
                                              GtkWidget       *widget,
                                              int             *minimum,
                                              int             *natural)
{
  int ypad, size;

  g_object_get (cell, "ypad", &ypad, NULL);

  size = MIN_HEIGHT + 2 * ypad;

  if (minimum != NULL)
    *minimum = size;
  if (natural != NULL)
    *natural = size;
}

static void
gtk_cell_renderer_graph_snapshot (GtkCellRenderer      *cell,
                                  GtkSnapshot          *snapshot,
                                  GtkWidget            *widget,
                                  const GdkRectangle   *background_area,
                                  const GdkRectangle   *cell_area,
                                  GtkCellRendererState  flags)
{
  GtkCellRendererGraph *graph = GTK_CELL_RENDERER_GRAPH (cell);
  GtkCellRendererGraphPrivate *priv = graph->priv;
  GtkStyleContext *context;
  double minimum, maximum, diff;
  double x, y, width, height;
  int xpad, ypad;
  cairo_t *cr;
  GdkRGBA color;
  guint i, n;

#define LINE_WIDTH 1.0

  if (priv->data == NULL)
    return;

  g_object_get (cell,
                "xpad", &xpad,
                "ypad", &ypad,
                NULL);

  if (priv->minimum == -G_MAXDOUBLE)
    minimum = gtk_graph_data_get_minimum (priv->data);
  else
    minimum = priv->minimum;

  if (priv->maximum == G_MAXDOUBLE)
    maximum = gtk_graph_data_get_maximum (priv->data);
  else
    maximum = priv->maximum;

  diff = maximum - minimum;

  context = gtk_widget_get_style_context (widget);
  gtk_style_context_get_color (context, &color);

  cr = gtk_snapshot_append_cairo (snapshot,
                                  &GRAPHENE_RECT_INIT (
                                      background_area->x, background_area->y,
                                      background_area->width, background_area->height
                                  ));

  cairo_set_line_width (cr, 1.0);

  x = background_area->x + xpad + LINE_WIDTH / 2.0;
  y = background_area->y + ypad + LINE_WIDTH / 2.0;
  width = background_area->width - 2 * xpad - LINE_WIDTH;
  height = background_area->height - 2 * ypad - LINE_WIDTH;

  cairo_move_to (cr, x, y + height);

  if (diff > 0)
    {
      n = gtk_graph_data_get_n_values (priv->data);
      for (i = 0; i < n; i++)
        {
          double val = gtk_graph_data_get_value (priv->data, i);

          val = (val - minimum) / diff;
          val = y + height - val * height;

          cairo_line_to (cr, x + width * i / (n - 1), val);
        }
    }

  cairo_line_to (cr, x + width, y + height);
  cairo_close_path (cr);

  gdk_cairo_set_source_rgba (cr, &color);
  cairo_stroke_preserve (cr);

  color.alpha *= 0.2;
  gdk_cairo_set_source_rgba (cr, &color);
  cairo_fill (cr);

  cairo_destroy (cr);
}

static void
gtk_cell_renderer_graph_class_init (GtkCellRendererGraphClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);

  object_class->dispose = gtk_cell_renderer_graph_dispose;
  object_class->get_property = gtk_cell_renderer_graph_get_property;
  object_class->set_property = gtk_cell_renderer_graph_set_property;

  cell_class->get_preferred_width = gtk_cell_renderer_graph_get_preferred_width;
  cell_class->get_preferred_height = gtk_cell_renderer_graph_get_preferred_height;
  cell_class->snapshot = gtk_cell_renderer_graph_snapshot;

  g_object_class_install_property (object_class,
                                   PROP_DATA,
                                   g_param_spec_object ("data",
                                                        "Data",
                                                        "The data to display",
                                                         GTK_TYPE_GRAPH_DATA,
                                                         G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));

  g_object_class_install_property (object_class,
                                   PROP_MINIMUM,
                                   g_param_spec_double ("minimum",
                                                        "Minimum",
                                                        "Minimum value to use (or -G_MAXDOUBLE for graph's value",
                                                        -G_MAXDOUBLE, G_MAXDOUBLE, -G_MAXDOUBLE,
                                                        G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));

  g_object_class_install_property (object_class,
                                   PROP_MINIMUM,
                                   g_param_spec_double ("maximum",
                                                        "Maximum",
                                                        "Maximum value to use (or G_MAXDOUBLE for graph's value",
                                                        -G_MAXDOUBLE, G_MAXDOUBLE, G_MAXDOUBLE,
                                                        G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
}

static void
gtk_cell_renderer_graph_init (GtkCellRendererGraph *cell)
{
  cell->priv = gtk_cell_renderer_graph_get_instance_private (cell);

  cell->priv->minimum = -G_MAXDOUBLE;
  cell->priv->maximum = G_MAXDOUBLE;
}

GtkCellRenderer *
gtk_cell_renderer_graph_new (void)
{
  return g_object_new (GTK_TYPE_CELL_RENDERER_GRAPH, NULL);
}
