/* Copyright (C) 2008 Igalia
 * Copyright (C) 2012-2021 MATE Developers
 *
 * This file is part of MATE Utils.
 *
 * MATE Utils 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.
 *
 * MATE Utils 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 MATE Utils.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Authors:
 *   Fabio Marzocca  <thesaltydog@gmail.com>
 *   Paolo Borelli <pborelli@katamail.com>
 *   Miguel Gomez <magomez@igalia.com>
 *   Eduardo Lima Mitev <elima@igalia.com>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <math.h>
#include <string.h>
#include <gtk/gtk.h>

#include "baobab-chart.h"
#include "baobab-treemap.h"

#define ITEM_TEXT_PADDING  3
#define ITEM_BORDER_WIDTH  1
#define ITEM_PADDING       6

#define ITEM_MIN_WIDTH    3
#define ITEM_MIN_HEIGHT   3

#define ITEM_SHOW_LABEL   TRUE

struct _BaobabTreemapPrivate
{
  guint max_visible_depth;
  gboolean more_visible_childs;
};

G_DEFINE_TYPE_WITH_PRIVATE (BaobabTreemap, baobab_treemap, BAOBAB_CHART_TYPE);

static void baobab_treemap_draw_rectangle (GtkWidget *chart,
                                           cairo_t *cr,
                                           gdouble x, gdouble y, gdouble width, gdouble height,
                                           BaobabChartColor fill_color,
                                           const char *text,
                                           gboolean show_text);
static void baobab_treemap_draw_item (GtkWidget *chart,
                                      cairo_t *cr,
                                      BaobabChartItem *item,
                                      gboolean highlighted);
static void baobab_treemap_calculate_item_geometry (GtkWidget *chart,
                                                    BaobabChartItem *item);
static gboolean baobab_treemap_is_point_over_item (GtkWidget *chart,
                                                   BaobabChartItem *item,
                                                   gdouble x,
                                                   gdouble y);
static void baobab_treemap_get_item_rectangle (GtkWidget *chart,
                                               BaobabChartItem *item);
guint baobab_treemap_can_zoom_in (GtkWidget *chart);
guint baobab_treemap_can_zoom_out (GtkWidget *chart);

static void
baobab_treemap_class_init (BaobabTreemapClass *class)
{
  BaobabChartClass *chart_class;

  chart_class = BAOBAB_CHART_CLASS (class);

  /* BaobabChart abstract methods */
  chart_class->draw_item = baobab_treemap_draw_item;
  chart_class->calculate_item_geometry = baobab_treemap_calculate_item_geometry;
  chart_class->is_point_over_item = baobab_treemap_is_point_over_item;
  chart_class->get_item_rectangle = baobab_treemap_get_item_rectangle;
  chart_class->can_zoom_in        = baobab_treemap_can_zoom_in;
  chart_class->can_zoom_out       = baobab_treemap_can_zoom_out;
}

static void
baobab_treemap_init (BaobabTreemap *chart)
{
  BaobabTreemapPrivate *priv;

  priv = baobab_treemap_get_instance_private (chart);

  chart->priv = priv;
}

static void
baobab_treemap_draw_rectangle (GtkWidget *chart,
                               cairo_t *cr,
                               gdouble x, gdouble y, gdouble width, gdouble height,
                               BaobabChartColor fill_color,
                               const char *text,
                               gboolean show_text)
{
  guint border = ITEM_BORDER_WIDTH;
  PangoRectangle rect;
  PangoLayout *layout;

  cairo_stroke (cr);

  cairo_set_line_width (cr, border);
  cairo_rectangle (cr, x + border, y + border,
                   width - border*2,
                   height - border*2);
  cairo_set_source_rgb (cr, fill_color.red, fill_color.green, fill_color.blue);
  cairo_fill_preserve (cr);
  cairo_set_source_rgb (cr, 0, 0, 0);
  cairo_stroke (cr);

  if ((show_text) && (ITEM_SHOW_LABEL))
    {
      layout = gtk_widget_create_pango_layout (chart, NULL);
      pango_layout_set_markup (layout, text, -1);
      pango_layout_get_pixel_extents (layout, NULL, &rect);

      if ((rect.width + ITEM_TEXT_PADDING*2 <= width) &&
          (rect.height + ITEM_TEXT_PADDING*2 <= height))
        {
          cairo_move_to (cr, x + width/2 - rect.width/2,
                         y + height/2 - rect.height/2);
          pango_cairo_show_layout (cr, layout);
        }

      g_object_unref (layout);
    }
}

static void
baobab_treemap_draw_item (GtkWidget *chart,
                          cairo_t *cr,
                          BaobabChartItem *item,
                          gboolean highlighted)
{
  cairo_rectangle_t * rect;
  BaobabChartColor fill_color;
  GtkAllocation allocation;
  gdouble width, height;

  rect = (cairo_rectangle_t *) item->data;

  gtk_widget_get_allocation (chart, &allocation);

  if (item->depth % 2 != 0)
    {
      baobab_chart_get_item_color (&fill_color, rect->x/allocation.width*200,
                                   item->depth, highlighted);
      width = rect->width - ITEM_PADDING;
      height = rect->height;
    }
  else
    {
      baobab_chart_get_item_color (&fill_color, rect->y/allocation.height*200,
                                   item->depth, highlighted);
      width = rect->width;
      height = rect->height - ITEM_PADDING;
    }

  baobab_treemap_draw_rectangle (chart,
                                 cr,
                                 rect->x,
                                 rect->y,
                                 width,
                                 height,
                                 fill_color,
                                 item->name,
                                 (! item->has_visible_children) );
}

static void
baobab_treemap_calculate_item_geometry (GtkWidget *chart,
                                        BaobabChartItem *item)
{
  BaobabTreemapPrivate *priv;
  cairo_rectangle_t p_area;
  static cairo_rectangle_t *rect;
  gdouble width, height;
  BaobabChartItem *parent = NULL;
  GtkAllocation allocation;

  priv = BAOBAB_TREEMAP (chart)->priv;

  if (item->depth == 0)
    {
      priv->max_visible_depth = 0;
      priv->more_visible_childs = FALSE;
    }

  item->visible = FALSE;

  if (item->parent == NULL)
    {
      gtk_widget_get_allocation (chart, &allocation);
      p_area.x = 0 - ITEM_PADDING/2;
      p_area.y = 0 - ITEM_PADDING/2;
      p_area.width = allocation.width + ITEM_PADDING * 2;
      p_area.height = allocation.height + ITEM_PADDING;
    }
  else
    {
      parent = (BaobabChartItem *) item->parent->data;
      g_memmove (&p_area, parent->data, sizeof (cairo_rectangle_t));
    }

  if (item->data == NULL)
    item->data = g_new (cairo_rectangle_t, 1);

  rect = (cairo_rectangle_t *) item->data;

  if (item->depth % 2 != 0)
    {
      width = p_area.width - ITEM_PADDING;

      rect->x = p_area.x + (item->rel_start * width / 100) + ITEM_PADDING;
      rect->y = p_area.y + ITEM_PADDING;
      rect->width = width * item->rel_size / 100;
      rect->height = p_area.height - ITEM_PADDING * 3;
    }
  else
    {
      height = p_area.height - ITEM_PADDING;

      rect->x = p_area.x + ITEM_PADDING;
      rect->y = p_area.y + (item->rel_start * height / 100) + ITEM_PADDING;
      rect->width = p_area.width - ITEM_PADDING * 3;
      rect->height = height * item->rel_size / 100;
    }

  if ((rect->width - ITEM_PADDING < ITEM_MIN_WIDTH) ||
      (rect->height - ITEM_PADDING < ITEM_MIN_HEIGHT))
    return;

  rect->x = floor (rect->x) + 0.5;
  rect->y = floor (rect->y) + 0.5;
  rect->width = floor (rect->width);
  rect->height = floor (rect->height);

  item->visible = TRUE;

  if (parent != NULL)
    parent->has_visible_children = TRUE;

  baobab_treemap_get_item_rectangle (chart, item);

  if (item->depth == baobab_chart_get_max_depth (chart) + 1)
    priv->more_visible_childs = TRUE;
  else
    priv->max_visible_depth = MAX (priv->max_visible_depth, item->depth);
}

static gboolean
baobab_treemap_is_point_over_item (GtkWidget *chart,
                                   BaobabChartItem *item,
                                   gdouble x,
                                   gdouble y)
{
  GdkRectangle *rect;

  rect = &item->rect;
  return ((x >= rect->x) && (x <= rect->x + rect->width) &&
          (y >= rect->y) && (y <= rect->y + rect->height));
}

static void
baobab_treemap_get_item_rectangle (GtkWidget *chart,
                                   BaobabChartItem *item)
{
  cairo_rectangle_t *_rect;

  _rect = (cairo_rectangle_t *) item->data;

  item->rect.x = (int) _rect->x;
  item->rect.y = (int) _rect->y;
  if (item->depth % 2 != 0)
    {
      item->rect.width = (int) _rect->width - ITEM_PADDING;
      item->rect.height = (int) _rect->height;
    }
  else
    {
      item->rect.width = (int) _rect->width;
      item->rect.height = (int) _rect->height - ITEM_PADDING;
    }

}

guint
baobab_treemap_can_zoom_in (GtkWidget *chart)
{
  BaobabTreemapPrivate *priv;

  priv = BAOBAB_TREEMAP (chart)->priv;

  return MAX (0, (gint) (priv->max_visible_depth - 1));
}

guint
baobab_treemap_can_zoom_out (GtkWidget *chart)
{
  BaobabTreemapPrivate *priv;

  priv = BAOBAB_TREEMAP (chart)->priv;

  return priv->more_visible_childs ? 1 : 0;
}

/* Public functions start here */

/**
 * baobab_treemap_new:
 *
 * Constructor for the baobab_treemap class
 *
 * Returns: a new #BaobabTreemap object
 *
 **/
GtkWidget*
baobab_treemap_new (void)
{
  return g_object_new (BAOBAB_TREEMAP_TYPE, NULL);
}
