/* Pango
 * Rendering routines to Clutter
 *
 * Copyright (C) 2006 Matthew Allum <mallum@o-hand.com>
 * Copyright (C) 2006 Marc Lehmann <pcg@goof.com>
 * Copyright (C) 2004 Red Hat Software
 * Copyright (C) 2000 Tor Lillqvist
 *
 * This file is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This file 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <math.h>

#include "pangoclutter.h"
#include "pangoclutter-private.h"
#include "../clutter-debug.h"

#include "cogl.h"

/*
 * Texture cache support code
 */

#define TC_WIDTH  256
#define TC_HEIGHT 256
#define TC_ROUND  4

typedef struct {
  guint name;
  int x, y, w, h;
} tc_area;

typedef struct tc_texture {
  struct tc_texture *next;
  COGLuint name;
  int avail;
} tc_texture;

typedef struct tc_slice {
  guint name;
  int avail, y;
} tc_slice;

static int tc_generation = 0;
static tc_slice slices[TC_HEIGHT / TC_ROUND];
static tc_texture *first_texture;

static void
tc_clear ()
{
  int i;

  for (i = TC_HEIGHT / TC_ROUND; i--; )
    slices [i].name = 0;

  while (first_texture)
    {
      tc_texture *next = first_texture->next;
      cogl_textures_destroy (1, &first_texture->name);
      g_slice_free (tc_texture, first_texture);
      first_texture = next;
    }

  ++tc_generation;
}

static void
tc_get (tc_area *area, int width, int height)
{
  int       slice_height;
  tc_slice *slice;

  area->w = width;
  area->h = height;

  /* Provide for blank rows/columns of pixels between adjecant glyphs in the
   * texture cache to avoid bilinear interpolation spillage at edges of glyphs.
   */
  width += 1;
  height += 1;

  slice_height = MIN (height + TC_ROUND - 1, TC_HEIGHT) & ~(TC_ROUND - 1);
  slice = slices + slice_height / TC_ROUND;

  width = MIN (width, TC_WIDTH);

  if (!slice->name || slice->avail < width)
    {
      /* try to find a texture with enough space */
      tc_texture *tex, *match = 0;

      for (tex = first_texture; tex; tex = tex->next)
        if (tex->avail >= slice_height && (!match || match->avail > tex->avail))
          match = tex;

      /* create a new texture if necessary */
      if (!match)
        {
	  COGLenum texture_target_type = CGL_TEXTURE_2D;

	  CLUTTER_NOTE (PANGO, "creating new texture %i x %i",
			TC_WIDTH, TC_HEIGHT);

	  /* Use NPOTS if available as it simply makes shaders easier to
	   * work with on text.
	  */
	  if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_RECTANGLE))
	    texture_target_type = CGL_TEXTURE_RECTANGLE_ARB;

          match = g_slice_new (tc_texture);
          match->next = first_texture;
          first_texture = match;
          match->avail = TC_HEIGHT;

	  cogl_textures_create (1, &match->name);

	  cogl_texture_bind (texture_target_type, match->name);

        /* We might even want to use mipmapping instead of CGL_LINEAR here
         * that should allow rerendering of glyphs to look nice even at scales
         * far below 50%.
         */
	  cogl_texture_set_filters (texture_target_type,
				    CGL_LINEAR, CGL_NEAREST);

	  cogl_texture_image_2d (texture_target_type,
				 CGL_ALPHA,
				 TC_WIDTH,
				 TC_HEIGHT,
				 CGL_ALPHA,
				 CGL_UNSIGNED_BYTE,
				 NULL);
        }

      match->avail -= slice_height;

      slice->name  = match->name;
      slice->avail = TC_WIDTH;
      slice->y     = match->avail;
    }

  slice->avail -= width;

  area->name = slice->name;
  area->x    = slice->avail;
  area->y    = slice->y;
}

static void
tc_put (tc_area *area)
{
  /* our management is too primitive to support this operation yet */
}

/*******************/


#define PANGO_CLUTTER_RENDERER_CLASS(klass)                       \
          (G_TYPE_CHECK_CLASS_CAST ((klass),                      \
                                    PANGO_TYPE_CLUTTER_RENDERER,  \
                                    PangoClutterRendererClass))

#define PANGO_IS_CLUTTER_RENDERER_CLASS(klass)                    \
          (G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO_TYPE_CLUTTER_RENDERER))

#define PANGO_CLUTTER_RENDERER_GET_CLASS(obj)                      \
          (G_TYPE_INSTANCE_GET_CLASS ((obj),                       \
				      PANGO_TYPE_CLUTTER_RENDERER, \
				      PangoClutterRendererClass))

typedef struct {
  PangoRendererClass parent_class;
} PangoClutterRendererClass;

struct _PangoClutterRenderer
{
  PangoRenderer parent_instance;
  ClutterColor  color;
  int           flags;
  guint         curtex; /* current texture */
  COGLenum      texture_target_type; /* 2D or Rect */
};

G_DEFINE_TYPE (PangoClutterRenderer,   \
	       pango_clutter_renderer, \
	       PANGO_TYPE_RENDERER)

typedef struct
{
  guint8 *bitmap;
  int     width, stride, height, top, left;
} Glyph;

static void *
temp_buffer (size_t size)
{
  static char *buffer;
  static size_t alloc;

  if (size > alloc)
    {
      size = (size + 4095) & ~4095;
      g_free (buffer);
      alloc = size;
      buffer = g_malloc (size);
    }

  return buffer;
}

static void
render_box (Glyph *glyph, int width, int height, int top)
{
  int i;
  int left = 0;

  if (height > 2)
    {
      height -= 2;
      top++;
    }

  if (width > 2)
    {
      width -= 2;
      left++;
    }

  glyph->stride = (width + 3) & ~3;
  glyph->width  = width;
  glyph->height = height;
  glyph->top    = top;
  glyph->left   = left;

  glyph->bitmap = temp_buffer (width * height);
  memset (glyph->bitmap, 0, glyph->stride * height);

  for (i = width; i--; )
    glyph->bitmap [i]
      = glyph->bitmap [i + (height - 1) * glyph->stride] = 0xff;

  for (i = height; i--; )
    glyph->bitmap [i * glyph->stride]
      = glyph->bitmap [i * glyph->stride + (width - 1)] = 0xff;
}

static void
font_render_glyph (Glyph *glyph, PangoFont *font, int glyph_index)
{
  FT_Face face;

  if (glyph_index & PANGO_GLYPH_UNKNOWN_FLAG)
    {
      PangoFontMetrics *metrics;

      if (!font)
	goto generic_box;

      metrics = pango_font_get_metrics (font, NULL);
      if (!metrics)
	goto generic_box;

      render_box (glyph, PANGO_PIXELS (metrics->approximate_char_width),
		         PANGO_PIXELS (metrics->ascent + metrics->descent),
		         PANGO_PIXELS (metrics->ascent));

      pango_font_metrics_unref (metrics);

      return;
    }

  face = pango_clutter_font_get_face (font);

  if (face)
    {
      PangoClutterFont *glfont = (PangoClutterFont *)font;

      FT_Load_Glyph (face, glyph_index, glfont->load_flags);
      FT_Render_Glyph (face->glyph, ft_render_mode_normal);

      glyph->width  = face->glyph->bitmap.width;
      glyph->stride = face->glyph->bitmap.pitch;
      glyph->height = face->glyph->bitmap.rows;
      glyph->top    = face->glyph->bitmap_top;
      glyph->left   = face->glyph->bitmap_left;
      glyph->bitmap = face->glyph->bitmap.buffer;
    }
  else
    generic_box:
      render_box (glyph, PANGO_UNKNOWN_GLYPH_WIDTH,
		  PANGO_UNKNOWN_GLYPH_HEIGHT, PANGO_UNKNOWN_GLYPH_HEIGHT);
}

typedef struct glyph_info
{
  tc_area tex;
  int     left, top;
  int     generation;
}
glyph_info;

static void
free_glyph_info (glyph_info *g)
{
  tc_put (&g->tex);
  g_slice_free (glyph_info, g);
}

static void
draw_glyph (PangoRenderer *renderer_,
	    PangoFont     *font,
	    PangoGlyph     glyph,
	    double         xd,
	    double         yd)
{
  PangoClutterRenderer *renderer = PANGO_CLUTTER_RENDERER (renderer_);
  glyph_info           *g;
  gint                  x = (gint)xd, y = (gint)yd;

  if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
    {
      glyph = pango_clutter_get_unknown_glyph (font);

      if (glyph == PANGO_GLYPH_EMPTY)
	glyph = PANGO_GLYPH_UNKNOWN_FLAG;
    }

  g = _pango_clutter_font_get_cache_glyph_data (font, glyph);

  if (!g || g->generation != tc_generation)
    {
      Glyph bm;
      font_render_glyph (&bm, font, glyph);

      if (bm.width < 1 || bm.height < 1 || bm.bitmap == NULL)
        {
          /* Safety on */
          if (g)
            {
              x += g->left;
              y -= g->top;
            }
          return;
        }

      if (bm.height > TC_HEIGHT)
        {
          g_warning ("%s: Glyph too large for cache, increase TC_HEIGHT",
                     G_STRLOC);
        }

      if (g)
        g->generation = tc_generation;
      else
        {
          g = g_slice_new0 (glyph_info);

          _pango_clutter_font_set_glyph_cache_destroy
	                        (font, (GDestroyNotify)free_glyph_info);
          _pango_clutter_font_set_cache_glyph_data (font, glyph, g);
        }

      tc_get (&g->tex, bm.width, bm.height);

      g->left = bm.left;
      g->top  = bm.top;

      CLUTTER_NOTE (PANGO, "cache fail; subimage2d %i", glyph);

      cogl_texture_bind (renderer->texture_target_type, g->tex.name);

      cogl_texture_set_alignment (renderer->texture_target_type,
				  1, bm.stride);

      cogl_texture_sub_image_2d (renderer->texture_target_type,
				 g->tex.x,
				 g->tex.y,
				 bm.width,
				 bm.height,
				 CGL_ALPHA,
				 CGL_UNSIGNED_BYTE,
				 bm.bitmap);

      glTexParameteri (renderer->texture_target_type,
		       GL_GENERATE_MIPMAP, FALSE);

      renderer->curtex = g->tex.name;
    }
  else CLUTTER_NOTE (PANGO, "cache succsess %i\n", glyph);

  x += g->left;
  y -= g->top;

  if (g->tex.name != renderer->curtex)
    {
      cogl_texture_bind (renderer->texture_target_type, g->tex.name);
      renderer->curtex = g->tex.name;
    }

  if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_RECTANGLE))
    {
      cogl_texture_quad (x,
			 x + g->tex.w,
			 y,
			 y + g->tex.h,
			 CLUTTER_INT_TO_FIXED (g->tex.x),
			 CLUTTER_INT_TO_FIXED (g->tex.y),
			 CLUTTER_INT_TO_FIXED (g->tex.w + g->tex.x),
			 CLUTTER_INT_TO_FIXED (g->tex.h + g->tex.y));
    }
  else
    {
      ClutterFixed fx, fy;

      fx = CLUTTER_INT_TO_FIXED (g->tex.x) / TC_WIDTH;
      fy = CLUTTER_INT_TO_FIXED (g->tex.y) / TC_HEIGHT;

      cogl_texture_quad (x,
			 x + g->tex.w,
			 y,
			 y + g->tex.h,
			 fx,
			 fy,
			 CLUTTER_INT_TO_FIXED (g->tex.w) / TC_WIDTH + fx,
			 CLUTTER_INT_TO_FIXED (g->tex.h) / TC_HEIGHT + fy);
    }
}

static void
draw_trapezoid (PangoRenderer   *renderer_,
		PangoRenderPart  part,
		double           y01,
		double           x11,
		double           x21,
		double           y02,
		double           x12,
		double           x22)
{
  PangoClutterRenderer *renderer = (PangoClutterRenderer *)renderer_;

  if (renderer->curtex)
    {
      /* glEnd (); */
      renderer->curtex = 0;
    }

  /* Turn texturing off */
  cogl_enable (CGL_ENABLE_BLEND);

  cogl_trapezoid ((gint) y01,
		  (gint) x11,
		  (gint) x21,
		  (gint) y02,
		  (gint) x12,
		  (gint) x22);

  /* Turn it back on again */
  if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_RECTANGLE))
    cogl_enable (CGL_ENABLE_TEXTURE_RECT|CGL_ENABLE_BLEND);
  else
    cogl_enable (CGL_ENABLE_TEXTURE_2D|CGL_ENABLE_BLEND);
}


void
pango_clutter_render_layout_subpixel (PangoLayout *layout,
				      int           x,
				      int           y,
				      ClutterColor *color,
				      int           flags)
{
  PangoContext  *context;
  PangoFontMap  *fontmap;
  PangoRenderer *renderer;

  context = pango_layout_get_context (layout);
  fontmap = pango_context_get_font_map (context);
  renderer = _pango_clutter_font_map_get_renderer
                     (PANGO_CLUTTER_FONT_MAP (fontmap));

  memcpy (&(PANGO_CLUTTER_RENDERER (renderer)->color),
	  color, sizeof(ClutterColor));

  pango_renderer_draw_layout (renderer, layout, x, y);
}

void
pango_clutter_render_layout (PangoLayout  *layout,
			     int           x,
			     int           y,
			     ClutterColor *color,
			     int           flags)
{
  pango_clutter_render_layout_subpixel (layout,
					x * PANGO_SCALE,
					y * PANGO_SCALE,
					color,
					flags);
}

void
pango_clutter_render_layout_line (PangoLayoutLine *line,
				  int              x,
				  int              y,
				  ClutterColor    *color)
{
  PangoContext  *context;
  PangoFontMap  *fontmap;
  PangoRenderer *renderer;

  context = pango_layout_get_context (line->layout);
  fontmap = pango_context_get_font_map (context);
  renderer = _pango_clutter_font_map_get_renderer
                     (PANGO_CLUTTER_FONT_MAP (fontmap));

  memcpy (&(PANGO_CLUTTER_RENDERER (renderer)->color),
	  color, sizeof(ClutterColor));

  pango_renderer_draw_layout_line (renderer, line, x, y);
}

void
pango_clutter_render_clear_caches (void)
{
  tc_clear();
}

static void
pango_clutter_renderer_init (PangoClutterRenderer *renderer)
{
  renderer->texture_target_type = CGL_TEXTURE_2D;

  if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_RECTANGLE))
    renderer->texture_target_type = CGL_TEXTURE_RECTANGLE_ARB;

  memset (&renderer->color, 0xff, sizeof(ClutterColor));
}

static void
prepare_run (PangoRenderer *renderer, PangoLayoutRun *run)
{
  PangoClutterRenderer *glrenderer = (PangoClutterRenderer *)renderer;
  PangoColor           *fg = 0;
  GSList               *l;
  ClutterColor          col;

  renderer->underline = PANGO_UNDERLINE_NONE;
  renderer->strikethrough = FALSE;

  for (l = run->item->analysis.extra_attrs; l; l = l->next)
    {
      PangoAttribute *attr = l->data;

      switch (attr->klass->type)
	{
	case PANGO_ATTR_UNDERLINE:
	  renderer->underline = ((PangoAttrInt *)attr)->value;
	  break;

	case PANGO_ATTR_STRIKETHROUGH:
	  renderer->strikethrough = ((PangoAttrInt *)attr)->value;
	  break;

	case PANGO_ATTR_FOREGROUND:
          fg = &((PangoAttrColor *)attr)->color;
	  break;
	default:
	  break;
	}
    }

  if (fg)
    {
      col.red   = (fg->red   * 255) / 65535;
      col.green = (fg->green * 255) / 65535;
      col.blue  = (fg->blue  * 255) / 65535;
    }
  else
    {
      col.red   = glrenderer->color.red;
      col.green = glrenderer->color.green;
      col.blue  = glrenderer->color.blue;
    }

  col.alpha = glrenderer->color.alpha;

  if (glrenderer->flags & FLAG_INVERSE)
    {
      col.red   ^= 0xffU;
      col.green ^= 0xffU;
      col.blue   ^= 0xffU;
    }

  cogl_color(&col);
}

static void
draw_begin (PangoRenderer *renderer_)
{
  PangoClutterRenderer *renderer = (PangoClutterRenderer *)renderer_;

  renderer->curtex = 0;

  if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_RECTANGLE))
    cogl_enable (CGL_ENABLE_TEXTURE_RECT|CGL_ENABLE_BLEND);
  else
    cogl_enable (CGL_ENABLE_TEXTURE_2D|CGL_ENABLE_BLEND);
}

static void
draw_end (PangoRenderer *renderer_)
{
  /*
  PangoClutterRenderer *renderer = (PangoClutterRenderer *)renderer_;

  if (renderer->curtex)
    glEnd ();
  */
}

static void
pango_clutter_renderer_class_init (PangoClutterRendererClass *klass)
{
  PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass);

  renderer_class->draw_glyph     = draw_glyph;
  renderer_class->draw_trapezoid = draw_trapezoid;
  renderer_class->prepare_run    = prepare_run;
  renderer_class->begin          = draw_begin;
  renderer_class->end            = draw_end;
}