/* GDK - The GIMP Drawing Kit
 * Copyright (C) 2000 Red Hat, Inc. 
 *
 * 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 "gdkcolor.h"
#include "gdkgc.h"
#include "gdkpango.h"
#include "gdkprivate.h"

#define GDK_INFO_KEY "gdk-info"

typedef struct _GdkPangoContextInfo GdkPangoContextInfo;

struct _GdkPangoContextInfo
{
  GdkColormap *colormap;
};

static void gdk_pango_get_item_properties (PangoItem      *item,
					   PangoUnderline *uline,
                                           gint           *rise,
					   PangoAttrColor *fg_color,
					   gboolean       *fg_set,
					   PangoAttrColor *bg_color,
					   gboolean       *bg_set,
					   gboolean       *shape_set,
					   PangoRectangle *ink_rect,
					   PangoRectangle *logical_rect);

static void
gdk_pango_context_destroy (GdkPangoContextInfo *info)
{
  gdk_colormap_unref (info->colormap);
  g_free (info);
}

static GdkPangoContextInfo *
gdk_pango_context_get_info (PangoContext *context, gboolean create)
{
  GdkPangoContextInfo *info =
    g_object_get_qdata (G_OBJECT (context),
                        g_quark_try_string (GDK_INFO_KEY));
  if (!info && create)
    {
      info = g_new (GdkPangoContextInfo, 1);
      info->colormap = NULL;

      g_object_set_qdata_full (G_OBJECT (context),
                               g_quark_from_static_string (GDK_INFO_KEY),
                               info, (GDestroyNotify)gdk_pango_context_destroy);
    }

  return info;
}

static GdkGC *
gdk_pango_get_gc (PangoContext   *context,
		  PangoAttrColor *fg_color,
		  GdkGC          *base_gc)
{
  GdkPangoContextInfo *info;
  GdkColormap *colormap;
  GdkColor color;
  
  g_return_val_if_fail (context != NULL, NULL);

  info = gdk_pango_context_get_info (context, FALSE);

  if (info && info->colormap)
    colormap = info->colormap;
  else
    colormap = gdk_colormap_get_system();

  /* FIXME. FIXME. FIXME. Only works for true color */

  color.red = fg_color->red;
  color.green = fg_color->green;
  color.blue = fg_color->blue;
  
  if (gdk_colormap_alloc_color (colormap, &color, FALSE, TRUE))
    {
      GdkGC *result = gdk_gc_new (gdk_parent_root);
      gdk_gc_copy (result, base_gc);
      gdk_gc_set_foreground (result, &color);

      return result;
    }
  else
    return gdk_gc_ref (base_gc);
}

static void
gdk_pango_free_gc (PangoContext *context,
		   GdkGC        *gc)
{
  gdk_gc_unref (gc);
}

void
gdk_pango_context_set_colormap (PangoContext *context,
				GdkColormap  *colormap)
{
  GdkPangoContextInfo *info;
  
  g_return_if_fail (context != NULL);

  info = gdk_pango_context_get_info (context, TRUE);
  g_return_if_fail (info != NULL);
  
  if (info->colormap != colormap)
    {
      if (info->colormap)
	gdk_colormap_unref (info->colormap);

      info->colormap = colormap;
      
      if (info->colormap)
   	gdk_colormap_ref (info->colormap);
    }
}

/**
 * gdk_draw_layout_line:
 * @drawable:  the drawable on which to draw the line
 * @gc:        base graphics to use
 * @x:         the x position of start of string (in pixels)
 * @y:         the y position of baseline (in pixels)
 * @line:      a #PangoLayoutLine
 *
 * Render a #PangoLayoutLine onto an GDK drawable
 */
void 
gdk_draw_layout_line (GdkDrawable      *drawable,
		      GdkGC            *gc,
		      gint              x, 
		      gint              y,
		      PangoLayoutLine  *line)
{
  GSList *tmp_list = line->runs;
  PangoRectangle overall_rect;
  PangoRectangle logical_rect;
  PangoRectangle ink_rect;
  PangoContext *context;
  gint x_off = 0;
  gint rise = 0;
  
  g_return_if_fail (drawable != NULL);
  g_return_if_fail (gc != NULL);
  g_return_if_fail (line != NULL);

  context = pango_layout_get_context (line->layout);
  
  pango_layout_line_get_extents (line,NULL, &overall_rect);
  
  while (tmp_list)
    {
      PangoUnderline uline = PANGO_UNDERLINE_NONE;
      PangoLayoutRun *run = tmp_list->data;
      PangoAttrColor fg_color, bg_color;
      gboolean fg_set, bg_set, shape_set;
      GdkGC *fg_gc;
      gint risen_y;
      
      tmp_list = tmp_list->next;
      
      gdk_pango_get_item_properties (run->item, &uline,
                                     &rise,
                                     &fg_color, &fg_set,
                                     &bg_color, &bg_set,
                                     &shape_set,
                                     &ink_rect,
				     &logical_rect);

      /* we subtract the rise because X coordinates are upside down */
      risen_y = y - rise / PANGO_SCALE;
      
      if (!shape_set)
	{
	  if (uline == PANGO_UNDERLINE_NONE)
	    pango_glyph_string_extents (run->glyphs, run->item->analysis.font,
					NULL, &logical_rect);
	  else
	    pango_glyph_string_extents (run->glyphs, run->item->analysis.font,
					&ink_rect, &logical_rect);
	}

      if (bg_set)
	{
	  GdkGC *bg_gc = gdk_pango_get_gc (context, &bg_color, gc);

	  gdk_draw_rectangle (drawable, bg_gc, TRUE,
			      x + (x_off + logical_rect.x) / PANGO_SCALE,
			      risen_y + overall_rect.y / PANGO_SCALE,
			      logical_rect.width / PANGO_SCALE,
			      overall_rect.height / PANGO_SCALE);

	  gdk_pango_free_gc (context, bg_gc);
	}

      if (fg_set)
	fg_gc = gdk_pango_get_gc (context, &fg_color, gc);
      else
	fg_gc = gc;

      if (!shape_set)
	gdk_draw_glyphs (drawable, fg_gc, run->item->analysis.font,
			 x + x_off / PANGO_SCALE,
                         risen_y,
                         run->glyphs);

      switch (uline)
	{
	case PANGO_UNDERLINE_NONE:
	  break;
	case PANGO_UNDERLINE_DOUBLE:
	  gdk_draw_line (drawable, fg_gc,
			 x + (x_off + ink_rect.x) / PANGO_SCALE - 1,
                         risen_y + 4,
			 x + (x_off + ink_rect.x + ink_rect.width) / PANGO_SCALE, y + 4);
	  /* Fall through */
	case PANGO_UNDERLINE_SINGLE:
	  gdk_draw_line (drawable, fg_gc,
			 x + (x_off + ink_rect.x) / PANGO_SCALE - 1,
                         risen_y + 2,
			 x + (x_off + ink_rect.x + ink_rect.width) / PANGO_SCALE, y + 2);
	  break;
	case PANGO_UNDERLINE_LOW:
	  gdk_draw_line (drawable, fg_gc,
			 x + (x_off + ink_rect.x) / PANGO_SCALE - 1,
                         risen_y + (ink_rect.y + ink_rect.height) / PANGO_SCALE + 2,
			 x + (x_off + ink_rect.x + ink_rect.width) / PANGO_SCALE,
                         risen_y + (ink_rect.y + ink_rect.height) / PANGO_SCALE + 2);
	  break;
	}

      if (fg_set)
	gdk_pango_free_gc (context, fg_gc);

      x_off += logical_rect.width;
    }
}

/**
 * gdk_draw_layout:
 * @drawable:  the drawable on which to draw string
 * @gc:        base graphics context to use
 * @x:         the X position of the left of the layout (in pixels)
 * @y:         the Y position of the top of the layout (in pixels)
 * @layout:    a #PangoLayout
 *
 * Render a #PangoLayout onto a GDK drawable
 */
void 
gdk_draw_layout (GdkDrawable     *drawable,
		 GdkGC           *gc,
		 int              x, 
		 int              y,
		 PangoLayout     *layout)
{
  PangoRectangle logical_rect;
  GSList *tmp_list;
  PangoAlignment align;
  gint indent;
  gint width;
  gint y_offset = 0;
  gboolean first = FALSE;
  
  g_return_if_fail (drawable != NULL);
  g_return_if_fail (gc != NULL);
  g_return_if_fail (layout != NULL);

  indent = pango_layout_get_indent (layout);
  width = pango_layout_get_width (layout);
  align = pango_layout_get_alignment (layout);

  if (width == -1 && align != PANGO_ALIGN_LEFT)
    {
      pango_layout_get_extents (layout, NULL, &logical_rect);
      width = logical_rect.width;
    }
  
  tmp_list = pango_layout_get_lines (layout);
  while (tmp_list)
    {
      PangoLayoutLine *line = tmp_list->data;
      int x_offset;
      
      pango_layout_line_get_extents (line, NULL, &logical_rect);

      if (width != -1 && align == PANGO_ALIGN_RIGHT)
	x_offset = width - logical_rect.width;
      else if (width != -1 && align == PANGO_ALIGN_CENTER)
	x_offset = (width - logical_rect.width) / 2;
      else
	x_offset = 0;

      if (first)
	{
	  if (indent > 0)
	    {
	      if (align == PANGO_ALIGN_LEFT)
		x_offset += indent;
	      else
		x_offset -= indent;
	    }

	  first = FALSE;
	}
      else
	{
	  if (indent < 0)
	    {
	      if (align == PANGO_ALIGN_LEFT)
		x_offset -= indent;
	      else
		x_offset += indent;
	    }
	}
	  
      gdk_draw_layout_line (drawable, gc,
			    x + x_offset / PANGO_SCALE, y + (y_offset - logical_rect.y) / PANGO_SCALE,
			    line);

      y_offset += logical_rect.height;
      tmp_list = tmp_list->next;
    }
}

static void
gdk_pango_get_item_properties (PangoItem      *item,
			       PangoUnderline *uline,
                               gint           *rise,
			       PangoAttrColor *fg_color,
			       gboolean       *fg_set,
			       PangoAttrColor *bg_color,
			       gboolean       *bg_set,
			       gboolean       *shape_set,
			       PangoRectangle *ink_rect,
			       PangoRectangle *logical_rect)
{
  GSList *tmp_list = item->extra_attrs;

  if (fg_set)
    *fg_set = FALSE;
  
  if (bg_set)
    *bg_set = FALSE;

  if (shape_set)
    *shape_set = FALSE;

  if (rise)
    *rise = 0;
  
  while (tmp_list)
    {
      PangoAttribute *attr = tmp_list->data;

      switch (attr->klass->type)
	{
	case PANGO_ATTR_UNDERLINE:
	  if (uline)
	    *uline = ((PangoAttrInt *)attr)->value;
	  break;
	  
	case PANGO_ATTR_FOREGROUND:
	  if (fg_color)
	    *fg_color = *((PangoAttrColor *)attr);
	  if (fg_set)
	    *fg_set = TRUE;
	  
	  break;
	  
	case PANGO_ATTR_BACKGROUND:
	  if (bg_color)
	    *bg_color = *((PangoAttrColor *)attr);
	  if (bg_set)
	    *bg_set = TRUE;
	  
	  break;

	case PANGO_ATTR_SHAPE:
	  if (shape_set)
	    *shape_set = TRUE;
	  if (logical_rect)
	    *logical_rect = ((PangoAttrShape *)attr)->logical_rect;
	  if (ink_rect)
	    *ink_rect = ((PangoAttrShape *)attr)->ink_rect;
	  break;

        case PANGO_ATTR_RISE:
          if (rise)
            *rise = ((PangoAttrInt *)attr)->value;
          break;
          
	default:
	  break;
	}
      tmp_list = tmp_list->next;
    }
}

