#include "evas_common_soft16.h"

static always_inline void
_glyph_pt_mask_solid_solid(DATA16 *dst,
			   const DATA16 rgb565,
			   const DATA32 rgb565_unpack,
			   const DATA8 *mask)
{
   DATA8 alpha = *mask >> 3;

   if (alpha == 31) *dst = rgb565;
   else if (alpha > 0)
     {
	DATA32 d;

	d = RGB_565_UNPACK(*dst);
	d = RGB_565_UNPACKED_BLEND_UNMUL(rgb565_unpack, d, alpha);
	*dst = RGB_565_PACK(d);
     }
}

static void
_glyph_scanline_mask_solid_solid(DATA16 *dst,
				 int size,
				 const DATA16 rgb565,
				 const DATA32 rgb565_unpack,
				 const DATA8 *mask)
{
   DATA16 *start, *end;

   start = dst;
   pld(start, 0);
   pld(mask, 0);
   end = start + (size & ~3);

   while (start < end)
     {
	pld(start, 16);
	pld(mask, 4);
	UNROLL4({
	   _glyph_pt_mask_solid_solid(start, rgb565, rgb565_unpack, mask);
	   start++;
	   mask++;
	});
     }

   end = start + (size & 3);
   for (; start < end; start++, mask++)
      _glyph_pt_mask_solid_solid(start, rgb565, rgb565_unpack, mask);
}

static always_inline void
_glyph_pt_mask_transp_solid(DATA16 *dst,
			    DATA32 rgb565_unpack,
			    DATA8 alpha,
			    const DATA8 *mask)
{
   DATA32 a, b;
   int rel_alpha;

   rel_alpha = *mask >> 3;
   alpha = (alpha * rel_alpha) >> 5;
   if (alpha == 0)
     return;

   alpha++;

   a = ((rgb565_unpack * rel_alpha) >> 5) & RGB_565_UNPACKED_MASK;
   b = RGB_565_UNPACK(*dst);
   b = RGB_565_UNPACKED_BLEND(a, b, alpha);
   *dst = RGB_565_PACK(b);
}

static void
_glyph_scanline_mask_transp_solid(DATA16 *dst,
				  int size,
				  const DATA32 rgb565_unpack,
				  const DATA8 rel_alpha,
				  const DATA8 *mask)
{
   DATA16 *start, *end;

   start = dst;
   pld(start, 0);
   pld(mask, 0);
   end = start + (size & ~3);

   while (start < end)
     {
	pld(start, 16);
	pld(mask, 4);
	UNROLL4({
	   _glyph_pt_mask_transp_solid(start, rgb565_unpack, rel_alpha, mask);
	   start++;
	   mask++;
	});
     }

   end = start + (size & 3);
   for (; start < end; start++, mask++)
      _glyph_pt_mask_transp_solid(start, rgb565_unpack, rel_alpha, mask);
}

static inline void
_calc_ext(const Soft16_Image *dst, const RGBA_Draw_Context *dc,
	  Eina_Rectangle *ext)
{
   EINA_RECTANGLE_SET(ext, 0, 0, dst->cache_entry.w, dst->cache_entry.h);

   if (dc->clip.use)
     {
	int v;

	EINA_RECTANGLE_SET(ext, dc->clip.x, dc->clip.y, dc->clip.w, dc->clip.h);
	if (ext->x < 0)
	  {
	     ext->w += ext->x;
	     ext->x = 0;
	  }
	if (ext->y < 0)
	  {
	     ext->h += ext->y;
	     ext->y = 0;
	  }

	v = dst->cache_entry.w - ext->x;
	if (ext->w > v) ext->w = v;

	v = dst->cache_entry.h - ext->y;
	if (ext->h > v) ext->h = v;
     }
}

static inline void
_glyph_scanline(Soft16_Image *dst, const DATA8 *p_mask,
		const Eina_Rectangle ext, int dx, int dy, int max_x, int max_y,
		int w, DATA8 alpha, const DATA16 rgb565,
		const DATA32 rgb565_unpack)
{
   int size, in_x, in_w;
   DATA16 *p_pixels;

   if ((dx >= max_x) || (dy < ext.y) || (dy >= max_y)) return;

   in_x = 0;
   in_w = 0;

   if (dx + w > max_x) in_w += (dx + w) - max_x;

   if (dx < ext.x)
     {
	in_w += ext.x - dx;
	in_x = ext.x - dx;
	dx = ext.x;
     }

   size = w - in_w;
   p_pixels = dst->pixels + (dy * dst->stride) + dx;
   p_mask += in_x;

   if (size > 1)
     {
	if (alpha == 31)
	   _glyph_scanline_mask_solid_solid
	       (p_pixels, size, rgb565, rgb565_unpack, p_mask);
	else if (alpha != 0)
	   _glyph_scanline_mask_transp_solid
	       (p_pixels, size, rgb565_unpack, alpha, p_mask);
     }
   else if (size == 1)
     {
	if (alpha == 31)
	   _glyph_pt_mask_solid_solid(p_pixels, rgb565, rgb565_unpack, p_mask);
	else if (alpha != 0)
	   _glyph_pt_mask_transp_solid(p_pixels, rgb565_unpack, alpha, p_mask);
     }
}

static void
_soft16_font_glyph_draw_grayscale(Soft16_Image *dst,
				  RGBA_Draw_Context *dc __UNUSED__, RGBA_Font_Glyph *fg __UNUSED__,
				  int x, int y, DATA8 alpha, DATA16 rgb565,
				  const Eina_Rectangle ext, int bw, int bh,
				  int bpitch, const DATA8 *bitmap)
{
   const DATA32 rgb565_unpack = RGB_565_UNPACK(rgb565);
   int i, max_x, max_y;

   max_x = ext.x + ext.w;
   max_y = ext.y + ext.h;

   for (i = 0; i < bh; i++, bitmap += bpitch)
      _glyph_scanline(dst, bitmap, ext, x, y + i, max_x, max_y, bw,
		      alpha, rgb565, rgb565_unpack);
}

static inline void
_glyph_create_mask_line(DATA8 *mask, const DATA8 *bitmap, int w)
{
   const DATA8 bitrepl[2] = {0x0, 0xff};
   int i;

   for (i = 0; i < w; i += 8, bitmap++)
     {
	int j, size;
	DATA32 bits;

	if (i + 8 < w) size = 8;
	else           size = w - i;

	bits = *bitmap;

	for (j = size - 1; j >= 0; j--, mask++)
	  *mask = bitrepl[(bits >> j) & 0x1];
     }
}

static void
_soft16_font_glyph_draw_mono(Soft16_Image *dst,
			     RGBA_Draw_Context *dc __UNUSED__, RGBA_Font_Glyph *fg __UNUSED__,
			     int x, int y, DATA8 alpha, DATA16 rgb565,
			     const Eina_Rectangle ext, int bw, int bh,
			     int bpitch, const DATA8 *bitmap)
{
   const DATA32 rgb565_unpack = RGB_565_UNPACK(rgb565);
   DATA8 *mask;
   int i, max_x, max_y;

   max_x = ext.x + ext.w;
   max_y = ext.y + ext.h;

   mask = alloca(bpitch);
   for (i = 0; i < bh; i++, bitmap += bpitch)
     {
	_glyph_create_mask_line(mask, bitmap, bw);
	_glyph_scanline(dst, mask, ext, x, y + i, max_x, max_y, bw,
			alpha, rgb565, rgb565_unpack);
     }
}

void
evas_common_soft16_font_glyph_draw(void *data, void *dest __UNUSED__, void *context,
		       RGBA_Font_Glyph *fg, int x, int y)
{
   Soft16_Image *dst;
   RGBA_Draw_Context *dc;
   const DATA8 *bitmap;
   DATA8 alpha, r, g, b;
   DATA16 rgb565;
   Eina_Rectangle ext;
   int bpitch, bw, bh;

   dst = data;
   dc = context;

   alpha = A_VAL(&dc->col.col) >> 3;
   if (alpha == 0) return; /* precision is 5 bits, 3 bits lost */

   r = R_VAL(&dc->col.col) >> 3;
   g = G_VAL(&dc->col.col) >> 2;
   b = B_VAL(&dc->col.col) >> 3;

   if (r > alpha) r = alpha;
   if (g > (alpha << 1)) g = (alpha << 1);
   if (b > alpha) b = alpha;

   rgb565 = (r << 11) | (g << 5) | b;

   bitmap = fg->glyph_out->bitmap.buffer;
   bh = fg->glyph_out->bitmap.rows;
   bw = fg->glyph_out->bitmap.width;
   bpitch = fg->glyph_out->bitmap.pitch;
   if (bpitch < bw) bpitch = bw;

   _calc_ext(dst, dc, &ext);

   if ((fg->glyph_out->bitmap.num_grays == 256) &&
       (fg->glyph_out->bitmap.pixel_mode == ft_pixel_mode_grays))
      _soft16_font_glyph_draw_grayscale(dst, dc, fg, x, y, alpha, rgb565,
					ext, bw, bh, bpitch, bitmap);
   else
      _soft16_font_glyph_draw_mono(dst, dc, fg, x, y, alpha, rgb565,
				   ext, bw, bh, bpitch, bitmap);
}

void *
evas_common_soft16_font_glyph_new(void *data __UNUSED__, RGBA_Font_Glyph *fg __UNUSED__)
{
   return (void *)1; /* core requires != NULL to work */
}

void
evas_common_soft16_font_glyph_free(void *ext_dat __UNUSED__)
{
}
