/*
 * draw-layer.c
 * Copyright (C) 2021 Zwarf <zwarf@mail.de>
 *
 * 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 3 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/>.
 */


/*
 * Code mainly taken from:
 * https://gitlab.gnome.org/GNOME/libshumate/-/blob/main/shumate/shumate-path-layer.c
 * https://gitlab.gnome.org/GNOME/libshumate/-/blob/main/shumate/shumate-marker-layer.c
 * This is a combination of the 'ShumatePathLayer' and 'ShumateMarkerLayer' classes adjusted for the needs of PicPlanner.
 *
 * TODO:
 * Comment better what is happening inside the functions.
 */


#include "draw-layer.h"

static GdkRGBA DEFAULT_STROKE_COLOR = { 0.64, 0.0, 0.0, 1.0 };

typedef struct
{
  GdkRGBA *stroke_color;
  gboolean stroke;
  double stroke_width;

  double *nodes_coordinates;
  uint nodes_len;
} PicplannerDrawLayerPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (PicplannerDrawLayer, picplanner_draw_layer, SHUMATE_TYPE_LAYER);


static void
update_marker_visibility (PicplannerDrawLayer *layer,
                          PicplannerMarker    *marker)
{
  ShumateViewport *viewport;
  ShumateMapSource *map_source;
  gboolean within_viewport;
  double x, y, min_size, x_offset, y_offset, x_marker_offset, y_marker_offset;
  int marker_width, marker_height;
  int width, height;

  g_assert (PICPLANNER_IS_DRAW_LAYER (layer));

  viewport = shumate_layer_get_viewport (SHUMATE_LAYER (layer));
  map_source = shumate_viewport_get_reference_map_source (viewport);
  if (!map_source)
    return;

  x = picplanner_marker_get_x (marker);
  y = picplanner_marker_get_y (marker);
  x_marker_offset = picplanner_marker_get_x_offset (marker);
  y_marker_offset = picplanner_marker_get_y_offset (marker);

  width = gtk_widget_get_width (GTK_WIDGET (layer));
  height = gtk_widget_get_height (GTK_WIDGET (layer));

  if (width<height)
    {
      min_size = width;
      x_offset = 0;
      y_offset = (height-width)/2;
    }
  else
    {
      min_size = height;
      x_offset = (width-height)/2;
      y_offset = 0;
    }

  gtk_widget_measure (GTK_WIDGET (marker), GTK_ORIENTATION_HORIZONTAL, -1, 0, &marker_width, NULL, NULL);
  gtk_widget_measure (GTK_WIDGET (marker), GTK_ORIENTATION_VERTICAL, -1, 0, &marker_height, NULL, NULL);

  x = floorf (x*min_size + x_offset + x_marker_offset - marker_width/2.f);
  y = floorf (y*min_size + y_offset + y_marker_offset- marker_height/2.f);

  within_viewport = x > -marker_width && x <= width &&
                    y > -marker_height && y <= height &&
                    marker_width < width && marker_height < height;

  gtk_widget_set_child_visible (GTK_WIDGET (marker), within_viewport);

  if (within_viewport)
    {
      GtkAllocation marker_allocation;

      gtk_widget_get_allocation (GTK_WIDGET (marker), &marker_allocation);

      if (marker_allocation.x != (int)x || marker_allocation.y != (int)y)
        {
          gtk_widget_queue_allocate (GTK_WIDGET (layer));
        }
    }
}


static void
picplanner_draw_layer_size_allocate (GtkWidget  *widget,
                                     int        width,
                                     int        height,
                                     int        baseline)
{
  (void) baseline;
  PicplannerDrawLayer *self = PICPLANNER_DRAW_LAYER (widget);
  GtkAllocation allocation;
  GtkWidget *child;

  for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
       child != NULL;
       child = gtk_widget_get_next_sibling (child))
    {
      gboolean within_viewport;
      double x, y, min_size, x_offset, y_offset, x_marker_offset, y_marker_offset;
      int marker_width, marker_height;

      if (!gtk_widget_should_layout (child))
        continue;

      x = picplanner_marker_get_x (PICPLANNER_MARKER (child));
      y = picplanner_marker_get_y (PICPLANNER_MARKER (child));
      x_marker_offset = picplanner_marker_get_x_offset (PICPLANNER_MARKER (child));
      y_marker_offset = picplanner_marker_get_y_offset (PICPLANNER_MARKER (child));

      if (width<height)
      {
        min_size = width;
        x_offset = 0;
        y_offset = (height-width)/2;
      }
    else
      {
        min_size = height;
        x_offset = (width-height)/2;
        y_offset = 0;
      }

      gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, -1, 0, &marker_width, NULL, NULL);
      gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, -1, 0, &marker_height, NULL, NULL);

      x = floorf (x*min_size + x_offset + x_marker_offset - marker_width/2.f);
      y = floorf (y*min_size + y_offset + y_marker_offset- marker_height/2.f);

      allocation.x = x;
      allocation.y = y;
      allocation.width = marker_width;
      allocation.height = marker_height;

      within_viewport = x > -allocation.width && x <= width &&
                        y > -allocation.height && y <= height &&
                        allocation.width < width && allocation.height < height;

      gtk_widget_set_child_visible (child, within_viewport);

      if (within_viewport)
        gtk_widget_size_allocate (child, &allocation, -1);
    }
}


static void
picplanner_draw_layer_dispose (GObject *object)
{
  PicplannerDrawLayer *self = PICPLANNER_DRAW_LAYER (object);
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (self);
  ShumateViewport *viewport = shumate_layer_get_viewport (SHUMATE_LAYER (self));
  GtkWidget *child;

  g_signal_handlers_disconnect_by_data (viewport, self);

  if (priv->nodes_coordinates)
    picplanner_draw_layer_remove_all (PICPLANNER_DRAW_LAYER (object));

  while ((child = gtk_widget_get_first_child (GTK_WIDGET (object))))
    gtk_widget_unparent (child);


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


static void
picplanner_draw_layer_finalize (GObject *object)
{
  PicplannerDrawLayer *self = PICPLANNER_DRAW_LAYER (object);
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (self);

  g_clear_pointer (&priv->stroke_color, gdk_rgba_free);

  G_OBJECT_CLASS (picplanner_draw_layer_parent_class)->finalize (object);
}


static void
picplanner_draw_layer_constructed (GObject *object)
{
  G_OBJECT_CLASS (picplanner_draw_layer_parent_class)->constructed (object);
}



static void
picplanner_draw_layer_snapshot (GtkWidget   *widget,
                                GtkSnapshot *snapshot)
{
  PicplannerDrawLayer *self = (PicplannerDrawLayer *)widget;
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (self);
  int width, height, min_size, x_offset, y_offset;
  cairo_t *cr;
  GtkWidget *child;


  /*
   * Draw the lines given by the coordinates.
   */

  width = gtk_widget_get_allocated_width (widget);
  height = gtk_widget_get_allocated_height (widget);
  if (width<height)
    {
      min_size = width;
      x_offset = 0;
      y_offset = (height-width)/2;
    }
  else
    {
      min_size = height;
      x_offset = (width-height)/2;
      y_offset = 0;
    }

  if (!gtk_widget_get_visible (widget) || width <= 0 || height <= 0)
    return;

  cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT(0, 0, width, height));

  cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
  cairo_set_line_cap  (cr, CAIRO_LINE_CAP_ROUND);

  for (uint i = 0; i < priv->nodes_len; i++)
    {
      double x, y;
      x = priv->nodes_coordinates[i*2]*min_size+x_offset;
      y = priv->nodes_coordinates[i*2+1]*min_size+y_offset;
      cairo_line_to (cr, x, y);
    }

  if (priv->stroke)
    {
      gdk_cairo_set_source_rgba (cr, priv->stroke_color);
      cairo_set_line_width (cr, priv->stroke_width);
      cairo_stroke (cr);
    }

  cairo_destroy (cr);

  /*
   * Draw the children added to the layer.
   */
  for (child = gtk_widget_get_first_child (widget);
       child != NULL;
       child = gtk_widget_get_next_sibling (child))
    {
      gtk_widget_snapshot_child (widget, child, snapshot);
    }
}

static void
picplanner_draw_layer_class_init (PicplannerDrawLayerClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->finalize = picplanner_draw_layer_finalize;
  object_class->dispose = picplanner_draw_layer_dispose;
  object_class->constructed = picplanner_draw_layer_constructed;

  widget_class->snapshot = picplanner_draw_layer_snapshot;
  widget_class->size_allocate = picplanner_draw_layer_size_allocate;
}

static void
picplanner_draw_layer_init (PicplannerDrawLayer *self)
{
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (self);

  priv->stroke = TRUE;
  priv->stroke_width = 2.0;
  priv->nodes_coordinates = NULL;
  priv->nodes_len = 0;

  priv->stroke_color = gdk_rgba_copy (&DEFAULT_STROKE_COLOR);
}


PicplannerDrawLayer *
picplanner_draw_layer_new (ShumateViewport *viewport)
{
  return g_object_new (PICPLANNER_TYPE_DRAW_LAYER,
                       "viewport", viewport,
                       NULL);
}


void
picplanner_draw_layer_add_marker (PicplannerDrawLayer *layer,
                                  PicplannerMarker    *marker)
{
  g_return_if_fail (PICPLANNER_IS_DRAW_LAYER (layer));
  g_return_if_fail (PICPLANNER_IS_MARKER (marker));

  gtk_widget_insert_before (GTK_WIDGET(marker), GTK_WIDGET (layer), NULL);
  update_marker_visibility (layer, marker);
}


void
picplanner_draw_layer_add_node_coordinates (PicplannerDrawLayer *layer,
                                            double              x,
                                            double              y)
{
  g_return_if_fail (PICPLANNER_IS_DRAW_LAYER (layer));

  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (layer);

  priv->nodes_coordinates = (double *) realloc (priv->nodes_coordinates, sizeof (double) * (priv->nodes_len+1) * 2);

  priv->nodes_coordinates[priv->nodes_len*2] = x;
  priv->nodes_coordinates[priv->nodes_len*2+1] = y;

  priv->nodes_len ++;

  gtk_widget_queue_draw (GTK_WIDGET (layer));
}

void
picplanner_draw_layer_remove_all (PicplannerDrawLayer *layer)
{
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (layer);

  g_return_if_fail (PICPLANNER_IS_DRAW_LAYER (layer));

  GtkWidget *child;

  child = gtk_widget_get_first_child (GTK_WIDGET (layer));
  while (child)
    {
      GtkWidget *next = gtk_widget_get_next_sibling (child);

      g_signal_handlers_disconnect_by_data (child, layer);
      gtk_widget_unparent (child);

      child = next;
    }

  g_free (priv->nodes_coordinates);
  priv->nodes_coordinates = NULL;
  priv->nodes_len = 0;

  gtk_widget_queue_draw (GTK_WIDGET (layer));
}


void
picplanner_draw_layer_set_stroke_color (PicplannerDrawLayer *layer,
                                        const GdkRGBA       *color)
{
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (layer);

  g_return_if_fail (PICPLANNER_IS_DRAW_LAYER (layer));

  if (priv->stroke_color != NULL)
    gdk_rgba_free (priv->stroke_color);

  if (color == NULL)
    color = &DEFAULT_STROKE_COLOR;

  priv->stroke_color = gdk_rgba_copy (color);

  gtk_widget_queue_draw (GTK_WIDGET (layer));
}


GdkRGBA *
picplanner_draw_layer_get_stroke_color (PicplannerDrawLayer *layer)
{
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (layer);

  g_return_val_if_fail (PICPLANNER_IS_DRAW_LAYER (layer), NULL);

  return priv->stroke_color;
}


void
picplanner_draw_layer_set_stroke (PicplannerDrawLayer *layer,
                                  gboolean            value)
{
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (layer);

  g_return_if_fail (PICPLANNER_IS_DRAW_LAYER (layer));

  priv->stroke = value;

  gtk_widget_queue_draw (GTK_WIDGET (layer));
}


gboolean
picplanner_draw_layer_get_stroke (PicplannerDrawLayer *layer)
{
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (layer);

  g_return_val_if_fail (PICPLANNER_IS_DRAW_LAYER (layer), FALSE);

  return priv->stroke;
}


void
picplanner_draw_layer_set_stroke_width (PicplannerDrawLayer *layer,
                                        double              value)
{
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (layer);

  g_return_if_fail (PICPLANNER_IS_DRAW_LAYER (layer));

  priv->stroke_width = value;

  gtk_widget_queue_draw (GTK_WIDGET (layer));
}


double
picplanner_draw_layer_get_stroke_width (PicplannerDrawLayer *layer)
{
  PicplannerDrawLayerPrivate *priv = picplanner_draw_layer_get_instance_private (layer);

  g_return_val_if_fail (PICPLANNER_IS_DRAW_LAYER (layer), 0);

  return priv->stroke_width;
}
