#include "evas_common.h"
#include "evas_private.h"
#include "evas_blend_private.h"

#include "language/evas_bidi_utils.h" /*defines BIDI_SUPPORT if possible */
#include "evas_font_private.h" /* for Frame-Queuing support */

#include "evas_font_ot.h"

#define WORD_CACHE_MAXLEN	50
/* How many to cache */
#define WORD_CACHE_NWORDS 40

static int max_cached_words = WORD_CACHE_NWORDS;

struct prword 
{
   EINA_INLIST;
   struct cinfo *cinfo;
   Evas_Text_Props text_props;
   DATA8 *im;
   int roww;
   int width;
   int height;
   int baseline;
};

struct cinfo 
{
   FT_UInt index;
   struct 
     {
        int x, y;
     } pos;
   int posx;
   RGBA_Font_Glyph *fg;
   struct 
     {
        int w,h;
        int rows;
        unsigned char *data;
     } bm;
};


#if defined(METRIC_CACHE) || defined(WORD_CACHE)
LK(lock_words); // for word cache call
static Eina_Inlist *words = NULL;
static struct prword *evas_font_word_prerender(RGBA_Draw_Context *dc, const Evas_Text_Props *text_props);
#endif

EAPI void
evas_common_font_draw_init(void)
{
   char *p;
   int tmp;

   if ((p = getenv("EVAS_WORD_CACHE_MAX_WORDS")))
     {
	tmp = strtol(p,NULL,10);
	/* 0 to disable of course */
	if (tmp > -1 && tmp < 500){
	     max_cached_words = tmp;
	}
     }
}

#ifdef EVAS_FRAME_QUEUING
EAPI void
evas_common_font_draw_finish(void)
{ 
}
#endif

/* 
 * BiDi handling: We receive the shaped string + other props from text_props,
 * we need to reorder it so we'll have the visual string (the way we draw)
 * and then for kerning we have to switch the order of the kerning query (as the prev
 * is on the right, and not on the left).
 */
static void
evas_common_font_draw_internal(RGBA_Image *dst, RGBA_Draw_Context *dc, RGBA_Font *fn __UNUSED__, int x, int y,
                               const Evas_Text_Props *text_props, RGBA_Gfx_Func func, int ext_x, int ext_y, int ext_w, 
                               int ext_h, int im_w, int im_h __UNUSED__)
{
   DATA32 *im;
   FT_Face pface = NULL;
   RGBA_Font_Int *fi;
   EVAS_FONT_WALK_TEXT_INIT();

   fi = text_props->font_instance;
   if (fi)
     {
        evas_common_font_int_reload(fi);

        if (fi->src->current_size != fi->size)
          {
             FTLOCK();
             FT_Activate_Size(fi->ft.size);
             FTUNLOCK();
             fi->src->current_size = fi->size;
          }
     }


#if defined(METRIC_CACHE) || defined(WORD_CACHE)
   unsigned int len;

   len = text_props->len;

   if (len > 2 && (len < WORD_CACHE_MAXLEN))
     {
        struct prword *word;

        word =
          evas_font_word_prerender(dc, text_props);
        if (word)
          {
             int j, rowstart, rowend, xstart, xrun;

             im = dst->image.data;
             xrun = word->width;
             y -= word->baseline;
             xstart = 0;
             rowstart = 0;
             rowend = word->height;
             /* Clip to extent */
             if (x + xrun > ext_x + ext_w)
               {
                  xrun -= x + xrun - ext_x - ext_w;
               }
             if (x < ext_x) 
               {
                  int excess = ext_x - x;
                  xstart = excess - 1;
                  xrun -= excess;
                  x = ext_x;
               }
             if (y + rowend > ext_y + ext_h)
               {
                  rowend -= (y - ext_y + rowend - ext_h);
               }
             if (y < ext_y)
               {
                  int excess = ext_y - y;
                  rowstart += excess;
                //rowend -= excess;
                //      y = ext_y;
               }

             if (xrun < 1) return;
# ifdef WORD_CACHE
             if (word->im)
               {
                  for (j = rowstart ; j < rowend ; j ++)
                    {
                       func(NULL, word->im + (word->roww * j) + xstart, dc->col.col,
                            im + ((y + j) * im_w) + x, xrun);
                    }
                  return;
               }
# elif defined(METRIC_CACHE)
             unsigned int ind;

             y += word->baseline;
             for (ind = 0 ; ind < len ; ind ++)
               {
                  // FIXME Do we need to draw?
                  struct cinfo *ci = word->cinfo + ind;
                  for (j = rowstart ; j < rowend ; j ++)
                    {
                       if ((ci->fg->ext_dat) && (dc->font_ext.func.gl_draw))
                         {
                            /* ext glyph draw */
                            dc->font_ext.func.gl_draw(dc->font_ext.data,
                                                      (void *)dst,
                                                      dc, ci->fg,
                                                      x + ci->pos.x,
                                                      y - ci->bm.h + j);
                         }
                       else
                         {
                            func(NULL, word->im + (word->roww * j) + xstart,
                                 dc->col.col, im + ((y + j) * im_w) + x, xrun);
                         }
                    }
               }
             return;
# endif
          }
     }
#endif

   im = dst->image.data;

   EVAS_FONT_WALK_TEXT_START()
     {
        FT_UInt idx;
        RGBA_Font_Glyph *fg;
        int chr_x, chr_y, chr_w;

        if (!EVAS_FONT_WALK_IS_VISIBLE) continue;

        idx = EVAS_FONT_WALK_INDEX;

        LKL(fi->ft_mutex);
        fg = evas_common_font_int_cache_glyph_get(fi, idx);
        if (!fg)
          {
             LKU(fi->ft_mutex);
             continue;
          }

        pface = fi->src->ft.face;
        LKU(fi->ft_mutex);

        if (dc->font_ext.func.gl_new)
          {
             /* extension calls */
             fg->ext_dat = dc->font_ext.func.gl_new(dc->font_ext.data, fg);
             fg->ext_dat_free = dc->font_ext.func.gl_free;
          }

        chr_x = x + EVAS_FONT_WALK_PEN_X + EVAS_FONT_WALK_X_OFF + EVAS_FONT_WALK_X_BEAR;
        chr_y = y + EVAS_FONT_WALK_PEN_Y + EVAS_FONT_WALK_Y_OFF + EVAS_FONT_WALK_Y_BEAR;
        chr_w = EVAS_FONT_WALK_WIDTH;

        if (chr_x < (ext_x + ext_w))
          {
             DATA8 *data;
             int i, j, w, h;

             data = fg->glyph_out->bitmap.buffer;
             j = fg->glyph_out->bitmap.pitch;
             w = fg->glyph_out->bitmap.width;
             if (j < w) j = w;
             h = fg->glyph_out->bitmap.rows;
             /*
                if ((fg->glyph_out->bitmap.pixel_mode == ft_pixel_mode_grays)
                && (fg->glyph_out->bitmap.num_grays == 256)
                )
                */
               {
                  if ((j > 0) && (chr_x + w > ext_x))
                    {
                       if ((fg->ext_dat) && (dc->font_ext.func.gl_draw))
                         {
                            /* ext glyph draw */
                            dc->font_ext.func.gl_draw(dc->font_ext.data,
                                  (void *)dst,
                                  dc, fg, chr_x,
                                  y - (chr_y - y));
                         }
                       else
                         {
                            if ((fg->glyph_out->bitmap.num_grays == 256) &&
                                  (fg->glyph_out->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY))
                              {
                                 for (i = 0; i < h; i++)
                                   {
                                      int dx, dy;
                                      int in_x, in_w;

                                      in_x = 0;
                                      in_w = 0;
                                      dx = chr_x;
                                      dy = y - (chr_y - i - y);
#ifdef EVAS_SLI
                                      if (((dy) % dc->sli.h) == dc->sli.y)
#endif
                                        {
                                           if ((dx < (ext_x + ext_w)) &&
                                                 (dy >= (ext_y)) &&
                                                 (dy < (ext_y + ext_h)))
                                             {
                                                if (dx + w > (ext_x + ext_w))
                                                  in_w += (dx + w) - (ext_x + ext_w);
                                                if (dx < ext_x)
                                                  {
                                                     in_w += ext_x - dx;
                                                     in_x = ext_x - dx;
                                                     dx = ext_x;
                                                  }
                                                if (in_w < w)
                                                  {
                                                     func(NULL, data + (i * j) + in_x, dc->col.col,
                                                           im + (dy * im_w) + dx, w - in_w);
                                                  }
                                             }
                                        }
                                   }
                              }
                            else
                              {
                                 DATA8 *tmpbuf = NULL, *dp, *tp, bits;
                                 int bi, bj;
                                 const DATA8 bitrepl[2] = {0x0, 0xff};

                                 tmpbuf = alloca(w);
                                 for (i = 0; i < h; i++)
                                   {
                                      int dx, dy;
                                      int in_x, in_w, end;

                                      in_x = 0;
                                      in_w = 0;
                                      dx = chr_x;
                                      dy = y - (chr_y - i - y);
#ifdef EVAS_SLI
                                      if (((dy) % dc->sli.h) == dc->sli.y)
#endif
                                        {
                                           tp = tmpbuf;
                                           dp = data + (i * fg->glyph_out->bitmap.pitch);
                                           for (bi = 0; bi < w; bi += 8)
                                             {
                                                bits = *dp;
                                                if ((w - bi) < 8) end = w - bi;
                                                else end = 8;
                                                for (bj = 0; bj < end; bj++)
                                                  {
                                                     *tp = bitrepl[(bits >> (7 - bj)) & 0x1];
                                                     tp++;
                                                  }
                                                dp++;
                                             }
                                           if ((dx < (ext_x + ext_w)) &&
                                                 (dy >= (ext_y)) &&
                                                 (dy < (ext_y + ext_h)))
                                             {
                                                if (dx + w > (ext_x + ext_w))
                                                  in_w += (dx + w) - (ext_x + ext_w);
                                                if (dx < ext_x)
                                                  {
                                                     in_w += ext_x - dx;
                                                     in_x = ext_x - dx;
                                                     dx = ext_x;
                                                  }
                                                if (in_w < w)
                                                  {
                                                     func(NULL, tmpbuf + in_x, dc->col.col,
                                                           im + (dy * im_w) + dx, w - in_w);
                                                  }
                                             }
                                        }
                                   }
                              }
                         }
                    }
               }
          }
        else
          break;
     }
   EVAS_FONT_WALK_TEXT_END();
  evas_common_font_int_use_trim();
}

EAPI void
evas_common_font_draw(RGBA_Image *dst, RGBA_Draw_Context *dc, RGBA_Font *fn, int x, int y, const Evas_Text_Props *text_props)
{
   int ext_x, ext_y, ext_w, ext_h;
   int im_w, im_h;
   RGBA_Gfx_Func func;
   Cutout_Rects *rects;
   Cutout_Rect  *r;
   int c, cx, cy, cw, ch;
   int i;

   im_w = dst->cache_entry.w;
   im_h = dst->cache_entry.h;

   ext_x = 0; ext_y = 0; ext_w = im_w; ext_h = im_h;
   if (dc->clip.use)
     {
	ext_x = dc->clip.x;
	ext_y = dc->clip.y;
	ext_w = dc->clip.w;
	ext_h = 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;
	  }
	if ((ext_x + ext_w) > im_w)
	  ext_w = im_w - ext_x;
	if ((ext_y + ext_h) > im_h)
	  ext_h = im_h - ext_y;
     }
   if (ext_w <= 0) return;
   if (ext_h <= 0) return;

#ifdef EVAS_FRAME_QUEUING
   LKL(fn->lock);
#endif
//   evas_common_font_size_use(fn);
   func = evas_common_gfx_func_composite_mask_color_span_get(dc->col.col, dst, 1, dc->render_op);

   if (!dc->cutout.rects)
     {
        evas_common_font_draw_internal(dst, dc, fn, x, y, text_props,
                                       func, ext_x, ext_y, ext_w, ext_h,
                                       im_w, im_h);
     }
   else
     {
        c = dc->clip.use; cx = dc->clip.x; cy = dc->clip.y; cw = dc->clip.w; ch = dc->clip.h;
        evas_common_draw_context_clip_clip(dc, 0, 0, dst->cache_entry.w, dst->cache_entry.h);
        /* our clip is 0 size.. abort */
        if ((dc->clip.w > 0) && (dc->clip.h > 0))
          {
             rects = evas_common_draw_context_apply_cutouts(dc);
             for (i = 0; i < rects->active; ++i)
               {
                  r = rects->rects + i;
                  evas_common_draw_context_set_clip(dc, r->x, r->y, r->w, r->h);
                  evas_common_font_draw_internal(dst, dc, fn, x, y, text_props,
                                                 func, r->x, r->y, r->w, r->h,
                                                 im_w, im_h);
               }
             evas_common_draw_context_apply_clear_cutouts(rects);
          }
        dc->clip.use = c; dc->clip.x = cx; dc->clip.y = cy; dc->clip.w = cw; dc->clip.h = ch;
     }
#ifdef EVAS_FRAME_QUEUING
   LKU(fn->lock);
#endif
}

/* Only used if cache is on */
#if defined(METRIC_CACHE) || defined(WORD_CACHE)

static Eina_Bool
_evas_font_word_prerender_text_props_equal(const Evas_Text_Props *_a, const Evas_Text_Props *_b)
{
   Evas_Font_Glyph_Info *gl1, *gl2;
   size_t i;

   if ((_a->len != _b->len) ||
       (_a->font_instance != _b->font_instance))
     return EINA_FALSE;

   gl1 = _a->info->glyph + _a->start;
   gl2 = _b->info->glyph + _b->start;
   i = _a->len;
   for ( ; (i > 0) && (gl1->index == gl2->index) ; i--, gl1++, gl2++)
      ;

   return (i == 0);
}

static struct prword *
evas_font_word_prerender(RGBA_Draw_Context *dc, const Evas_Text_Props *text_props)
{
   struct cinfo *metrics;
   unsigned char *im;
   int width;
   int height, above, below, baseline, descent;
   unsigned int i,j;
   struct prword *w;
   int last_delta = 0;
   Eina_Unicode gl;
   struct cinfo *ci;
   unsigned int len = text_props->len;
   RGBA_Font_Int *fi = (RGBA_Font_Int *) text_props->font_instance;
   EVAS_FONT_WALK_TEXT_INIT();

# ifndef METRIC_CACHE
   gl = dc->font_ext.func.gl_new ? 1: 0;
   if (gl) return NULL;
# endif

   LKL(lock_words);
   EINA_INLIST_FOREACH(words,w)
     {
        if (_evas_font_word_prerender_text_props_equal(&w->text_props,
                                                       text_props))
          {
             words = eina_inlist_promote(words, EINA_INLIST_GET(w));
             LKU(lock_words);
             return w;
          }
     }
   LKU(lock_words);

   gl = dc->font_ext.func.gl_new ? 1: 0;

   above = 0; below = 0; baseline = 0; height = 0; descent = 0;

   /* First pass: Work out how big and populate */
   metrics = malloc(sizeof(struct cinfo) * len);
   ci = metrics;
   EVAS_FONT_WALK_TEXT_START()
     {
        FT_UInt index;
        RGBA_Font_Glyph *fg;
        index = EVAS_FONT_WALK_INDEX;
        LKL(fi->ft_mutex);
        fg = evas_common_font_int_cache_glyph_get(fi, index);
        if (!fg)
          {
             LKU(fi->ft_mutex);
             continue;
          }

        LKU(fi->ft_mutex);
        EVAS_FONT_WALK_TEXT_WORK();
        /* Currently broken with invisible chars if (!EVAS_FONT_WALK_IS_VISIBLE) continue; */
        ci->index = index;
        ci->fg = fg;

        if (gl)
          {
             ci->fg->ext_dat =dc->font_ext.func.gl_new(dc->font_ext.data,ci->fg);
             ci->fg->ext_dat_free = dc->font_ext.func.gl_free;
          }
        ci->bm.data = ci->fg->glyph_out->bitmap.buffer;
        ci->bm.w = MAX(ci->fg->glyph_out->bitmap.pitch,
                       ci->fg->glyph_out->bitmap.width);
        ci->bm.rows = ci->fg->glyph_out->bitmap.rows;
        ci->bm.h = ci->fg->glyph_out->top;
        above = ci->bm.rows - (ci->bm.rows - ci->bm.h);
        below = ci->bm.rows - ci->bm.h;
        if (below > descent) descent = below;
        if (above > baseline) baseline = above;
        ci->pos.x = EVAS_FONT_WALK_PEN_X + EVAS_FONT_WALK_X_OFF + EVAS_FONT_WALK_X_BEAR;
        ci->pos.y = EVAS_FONT_WALK_PEN_Y + EVAS_FONT_WALK_Y_OFF + EVAS_FONT_WALK_Y_BEAR;
        last_delta = EVAS_FONT_WALK_X_ADV -
           (ci->bm.w + ci->fg->glyph_out->left);
        ci++;
     }
   EVAS_FONT_WALK_TEXT_END();

   /* First loop done */
   width = EVAS_FONT_WALK_PEN_X;
   if (last_delta < 0)
     width -= last_delta;
   width = (width & 0x7) ? width + (8 - (width & 0x7)) : width;

   height = baseline + descent;
   if (!gl)
     {
        im = calloc(height, width);
        for (i = 0 ; i  < len ; i ++)
          {
             struct cinfo *ci = metrics + i;

             for (j = 0 ; j < ci->bm.rows ; j ++)
               {
                  int correction; /* Used to remove negative inset and such */
                  if (ci->pos.x < 0)
                    correction = -ci->pos.x;
                  else
                    correction = 0;

                  memcpy(im + ci->pos.x + (j + baseline - ci->bm.h) * width +
                        correction,
                        ci->bm.data + j * ci->bm.w + correction,
                        ci->bm.w - correction);
               }
          }
     }
   else 
     {
        im = NULL;
     }

   /* Save it */
   struct prword *save;

   save = malloc(sizeof(struct prword));
   save->cinfo = metrics;
   evas_common_text_props_content_copy_and_ref(&save->text_props, text_props);
   save->im = im;
   save->width = EVAS_FONT_WALK_PEN_X;
   if (last_delta < 0)
     save->width += last_delta;
   save->roww = width;
   save->height = height;
   save->baseline = baseline;
   LKL(lock_words);
   words = eina_inlist_prepend(words, EINA_INLIST_GET(save));

   /* Clean up if too long */
   if (eina_inlist_count(words) > max_cached_words)
     {
	struct prword *last = (struct prword *)(words->last);

        if (last) 
          {
             if (last->im) free(last->im);
             if (last->cinfo) free(last->cinfo);
             evas_common_text_props_content_unref(&last->text_props);
             words = eina_inlist_remove(words, EINA_INLIST_GET(last));
             free(last);
          }
     }
   LKU(lock_words);

   return save;
}
#endif
