/* GTK - The GIMP Toolkit
 * Copyright (C) 2011 Red Hat, Inc.
 *
 * Author: Cosimo Cecchi <cosimoc@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, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include "gtkshadowprivate.h"

#include "gtkstylecontextprivate.h"
#include "gtkthemingengineprivate.h"
#include "gtkpango.h"

typedef struct _GtkShadowElement GtkShadowElement;

struct _GtkShadowElement {
  gint16 hoffset;
  gint16 voffset;
  gint16 radius;
  gint16 spread;

  gboolean inset;

  GdkRGBA color;
  GtkSymbolicColor *symbolic_color;
};

static void
shadow_element_print (GtkShadowElement *element,
                      GString          *str)
{
  gchar *color_str;

  if (element->inset)
    g_string_append (str, "inset ");

  g_string_append_printf (str, "%d %d ",
                          (gint) element->hoffset,
                          (gint) element->voffset);

  if (element->radius != 0)
    g_string_append_printf (str, "%d ", (gint) element->radius);

  if (element->spread != 0)
    g_string_append_printf (str, "%d ", (gint) element->spread);

  if (element->symbolic_color != NULL)
    color_str = gtk_symbolic_color_to_string (element->symbolic_color);
  else
    color_str = gdk_rgba_to_string (&element->color);

  g_string_append (str, color_str);
  g_free (color_str);
}

static void
shadow_element_free (GtkShadowElement *element)
{
  if (element->symbolic_color != NULL)
    gtk_symbolic_color_unref (element->symbolic_color);

  g_slice_free (GtkShadowElement, element);
}

static GtkShadowElement *
shadow_element_new (gdouble hoffset,
                    gdouble voffset,
                    gdouble radius,
                    gdouble spread,
                    gboolean inset,
                    GdkRGBA *color,
                    GtkSymbolicColor *symbolic_color)
{
  GtkShadowElement *retval;

  retval = g_slice_new0 (GtkShadowElement);

  retval->hoffset = hoffset;
  retval->voffset = voffset;
  retval->radius = radius;
  retval->spread = spread;
  retval->inset = inset;

  if (symbolic_color != NULL)
    retval->symbolic_color = gtk_symbolic_color_ref (symbolic_color);

  if (color != NULL)
    retval->color = *color;

  return retval;
}                  

/****************
 * GtkShadow *
 ****************/

G_DEFINE_BOXED_TYPE (GtkShadow, _gtk_shadow,
                     _gtk_shadow_ref, _gtk_shadow_unref)

struct _GtkShadow {
  GList *elements;

  guint ref_count;
  gboolean resolved;
};

GtkShadow *
_gtk_shadow_new (void)
{
  GtkShadow *retval;

  retval = g_slice_new0 (GtkShadow);
  retval->ref_count = 1;
  retval->resolved = FALSE;

  return retval;
}

GtkShadow *
_gtk_shadow_ref (GtkShadow *shadow)
{
  g_return_val_if_fail (shadow != NULL, NULL);

  shadow->ref_count++;

  return shadow;
}

gboolean
_gtk_shadow_get_resolved (GtkShadow *shadow)
{
  return shadow->resolved;
}

void
_gtk_shadow_unref (GtkShadow *shadow)
{
  g_return_if_fail (shadow != NULL);

  shadow->ref_count--;

  if (shadow->ref_count == 0)
    {
      g_list_free_full (shadow->elements,
                        (GDestroyNotify) shadow_element_free);
      g_slice_free (GtkShadow, shadow);
    }
}

void
_gtk_shadow_append (GtkShadow        *shadow,
                    gdouble           hoffset,
                    gdouble           voffset,
                    gdouble           radius,
                    gdouble           spread,
                    gboolean          inset,
                    GtkSymbolicColor *color)
{
  GtkShadowElement *element;

  g_return_if_fail (shadow != NULL);
  g_return_if_fail (color != NULL);

  element = shadow_element_new (hoffset, voffset,
                                radius, spread, inset,
                                NULL, color);

  shadow->elements = g_list_append (shadow->elements, element);
}

GtkShadow *
_gtk_shadow_resolve (GtkShadow       *shadow,
                     GtkStyleContext *context)
{
  GtkShadow *resolved_shadow;
  GtkShadowElement *element, *resolved_element;
  GdkRGBA color;
  GList *l;

  if (shadow->resolved)
    return _gtk_shadow_ref (shadow);

  resolved_shadow = _gtk_shadow_new ();

  for (l = shadow->elements; l != NULL; l = l->next)
    {
      element = l->data;

      if (!_gtk_style_context_resolve_color (context,
                                             element->symbolic_color,
                                             &color))
        {
          _gtk_shadow_unref (resolved_shadow);
          return NULL;
        }

      resolved_element =
        shadow_element_new (element->hoffset, element->voffset,
                            element->radius, element->spread, element->inset,
                            &color, NULL);

      resolved_shadow->elements =
        g_list_append (resolved_shadow->elements, resolved_element);
    }

  resolved_shadow->resolved = TRUE;

  return resolved_shadow;
}

void
_gtk_shadow_print (GtkShadow *shadow,
                   GString   *str)
{
  gint length;
  GList *l;

  length = g_list_length (shadow->elements);

  if (length == 0)
    return;

  shadow_element_print (shadow->elements->data, str);

  if (length == 1)
    return;

  for (l = g_list_next (shadow->elements); l != NULL; l = l->next)
    {
      g_string_append (str, ", ");
      shadow_element_print (l->data, str);
    }
}

void
_gtk_text_shadow_paint_layout (GtkShadow       *shadow,
                               cairo_t         *cr,
                               PangoLayout     *layout)
{
  GList *l;
  GtkShadowElement *element;

  if (!cairo_has_current_point (cr))
    cairo_move_to (cr, 0, 0);

  /* render shadows starting from the last one,
   * and the others on top.
   */
  for (l = g_list_last (shadow->elements); l != NULL; l = l->prev)
    {
      element = l->data;

      cairo_save (cr);

      cairo_rel_move_to (cr, element->hoffset, element->voffset);
      gdk_cairo_set_source_rgba (cr, &element->color);
      _gtk_pango_fill_layout (cr, layout);

      cairo_rel_move_to (cr, -element->hoffset, -element->voffset);
      cairo_restore (cr);
  }
}

void
_gtk_icon_shadow_paint (GtkShadow *shadow,
			cairo_t *cr)
{
  GList *l;
  GtkShadowElement *element;
  cairo_pattern_t *pattern;

  for (l = g_list_last (shadow->elements); l != NULL; l = l->prev)
    {
      element = l->data;

      cairo_save (cr);
      pattern = cairo_pattern_reference (cairo_get_source (cr));
      gdk_cairo_set_source_rgba (cr, &element->color);

      cairo_translate (cr, element->hoffset, element->voffset);
      cairo_mask (cr, pattern);

      cairo_restore (cr);
      cairo_pattern_destroy (pattern);
    }
}

void
_gtk_icon_shadow_paint_spinner (GtkShadow *shadow,
                                cairo_t   *cr,
                                gdouble    radius,
                                gdouble    progress)
{
  GtkShadowElement *element;
  GList *l;

  for (l = g_list_last (shadow->elements); l != NULL; l = l->prev)
    {
      element = l->data;

      cairo_save (cr);

      cairo_translate (cr, element->hoffset, element->voffset);
      _gtk_theming_engine_paint_spinner (cr,
                                         radius, progress,
                                         &element->color);

      cairo_restore (cr);
    }
}

void
_gtk_box_shadow_render (GtkShadow           *shadow,
                        cairo_t             *cr,
                        const GtkRoundedBox *padding_box)
{
  GtkShadowElement *element;
  GtkRoundedBox box;
  GList *l;

  cairo_save (cr);
  cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);

  _gtk_rounded_box_path (padding_box, cr);
  cairo_clip (cr);

  /* render shadows starting from the last one,
   * and the others on top.
   */
  for (l = g_list_last (shadow->elements); l != NULL; l = l->prev)
    {
      element = l->data;

      if (!element->inset)
        continue;

      box = *padding_box;
      _gtk_rounded_box_move (&box, element->hoffset, element->voffset);
      _gtk_rounded_box_shrink (&box,
                               element->spread, element->spread,
                               element->spread, element->spread);

      _gtk_rounded_box_path (&box, cr);
      _gtk_rounded_box_clip_path (padding_box, cr);

      gdk_cairo_set_source_rgba (cr, &element->color);
      cairo_fill (cr);
  }

  cairo_restore (cr);
}
