#include "evas_gl_private.h"

// FIXME: this is a verbatim copy of the software poly renderer. it just
// use gl to draw 1 pixel high spans like software does. this is to make
// sure rendering correctness matches the software engine but also to save
// time in coming up with a good triangulation algorithm. if you want to
// feel free to turn this into a real triangulation system and use gl to its
// fullest, but as such polygons are used so little, it's not worth it.

typedef struct _RGBA_Span RGBA_Span;
typedef struct _RGBA_Edge RGBA_Edge;
typedef struct _RGBA_Vertex RGBA_Vertex;

struct _RGBA_Span
{
   EINA_INLIST;
   int x, y, w;
};

struct _RGBA_Edge
{
   double x, dx;
   int i;
};

struct _RGBA_Vertex
{
   double x, y;
   int i;
};

#define POLY_EDGE_DEL(_i)                                               \
   {                                                                       \
      int _j;                                                              \
      \
      for (_j = 0; (_j < num_active_edges) && (edges[_j].i != _i); _j++);  \
      if (_j < num_active_edges)                                           \
        {                                                                  \
           num_active_edges--;                                             \
           memmove(&(edges[_j]), &(edges[_j + 1]),                         \
                      (num_active_edges - _j) * sizeof(RGBA_Edge));           \
        }                                                                  \
   }

#define POLY_EDGE_ADD(_i, _y)                                           \
   {                                                                       \
      int _j;                                                              \
      float _dx;                                                           \
      RGBA_Vertex *_p, *_q;                                                \
      if (_i < (n - 1)) _j = _i + 1;                                       \
      else _j = 0;                                                         \
      if (point[_i].y < point[_j].y)                                       \
        {                                                                  \
           _p = &(point[_i]);                                              \
           _q = &(point[_j]);                                              \
        }                                                                  \
      else                                                                 \
        {                                                                  \
           _p = &(point[_j]);                                              \
           _q = &(point[_i]);                                              \
        }                                                                  \
      edges[num_active_edges].dx = _dx = (_q->x - _p->x) / (_q->y - _p->y); \
      edges[num_active_edges].x = (_dx * ((float)_y + 0.5 - _p->y)) + _p->x; \
      edges[num_active_edges].i = _i;                                      \
      num_active_edges++;                                                  \
   }

Evas_GL_Polygon *
evas_gl_common_poly_point_add(Evas_GL_Polygon *poly, int x, int y)
{
   Evas_GL_Polygon_Point *pt;

   pt = calloc(1, sizeof(Evas_GL_Polygon_Point));
   if (!pt) return NULL;

   if (!poly) poly = calloc(1, sizeof(Evas_GL_Polygon));
   if (!poly)
     {
        free(pt);
        return NULL;
     }

   pt->x = x;
   pt->y = y;
   poly->points = eina_list_append(poly->points, pt);
   poly->changed = 1;
   return poly;
}

Evas_GL_Polygon *
evas_gl_common_poly_points_clear(Evas_GL_Polygon *poly)
{
   if (!poly) return NULL;
   while (poly->points)
     {
	Evas_GL_Polygon_Point *pt;

	pt = poly->points->data;
	poly->points = eina_list_remove(poly->points, pt);
	free(pt);
     }
   free(poly);
   return NULL;
}

static int
polygon_point_sorter(const void *a, const void *b)
{
   RGBA_Vertex *p, *q;

   p = (RGBA_Vertex *)a;
   q = (RGBA_Vertex *)b;
   if (p->y <= q->y) return -1;
   return 1;
}

static int
polygon_edge_sorter(const void *a, const void *b)
{
   RGBA_Edge *p, *q;

   p = (RGBA_Edge *)a;
   q = (RGBA_Edge *)b;
   if (p->x <= q->x) return -1;
   return 1;
}

void
evas_gl_common_poly_draw(Evas_Engine_GL_Context *gc, Evas_GL_Polygon *poly, int dx, int dy)
{
   Cutout_Rect  *r;
   int c, cx, cy, cw, ch, cr, cg, cb, ca, i;
   int x = 0, y = 0, w = 0, h = 0;
   Evas_GL_Texture *mtex = NULL;
   Eina_Bool mask_smooth = EINA_FALSE;
   Eina_Bool mask_color = EINA_FALSE;
   int mx = 0, my = 0, mw = 0, mh = 0;
   Evas_GL_Image *mask;

   Eina_List *l;
   int n, k, num_active_edges, yy0, yy1, *sorted_index, j;
   RGBA_Edge *edges;
   RGBA_Vertex *point;
   Evas_GL_Polygon_Point *pt;
   Eina_Inlist *spans;

   /* save out clip info */
   c = gc->dc->clip.use; cx = gc->dc->clip.x; cy = gc->dc->clip.y; cw = gc->dc->clip.w; ch = gc->dc->clip.h;

   ca = (gc->dc->col.col >> 24) & 0xff;
   if (ca <= 0) return;
   cr = (gc->dc->col.col >> 16) & 0xff;
   cg = (gc->dc->col.col >> 8 ) & 0xff;
   cb = (gc->dc->col.col      ) & 0xff;

   mask = gc->dc->clip.mask;
   if (mask)
     {
        evas_gl_common_image_update(gc, mask);
        mtex = mask->tex;
        if (mtex && mtex->pt && mtex->pt->w && mtex->pt->h)
          {
             // canvas coords
             mx = gc->dc->clip.mask_x;
             my = gc->dc->clip.mask_y;
             mw = mask->w;
             mh = mask->h;
             mask_smooth = mask->scaled.smooth;
             mask_color = gc->dc->clip.mask_color;
          }
        else mtex = NULL;
     }

   n = eina_list_count(poly->points);
   if (n < 3) return;
   edges = malloc(sizeof(RGBA_Edge) * n);
   if (!edges) return;
   point = malloc(sizeof(RGBA_Vertex) * n);
   if (!point)
     {
        free(edges);
        return;
     }
   sorted_index = malloc(sizeof(int) * n);
   if (!sorted_index)
     {
        free(edges);
        free(point);
        return;
     }

   k = 0;
   EINA_LIST_FOREACH(poly->points, l, pt)
     {
        point[k].x = pt->x + dx;
        point[k].y = pt->y + dy;
        point[k].i = k;
        k++;
     }
   qsort(point, n, sizeof(RGBA_Vertex), polygon_point_sorter);
   for (k = 0; k < n; k++) sorted_index[k] = point[k].i;
   k = 0;

   EINA_LIST_FOREACH(poly->points, l, pt)
     {
        point[k].x = pt->x + dx;
        point[k].y = pt->y + dy;
        point[k].i = k;
        k++;
     }

   yy0 = MAX(cy, ceil(point[sorted_index[0]].y - 0.5));
   yy1 = MIN(cy + ch - 1, floor(point[sorted_index[n - 1]].y - 0.5));

   k = 0;
   num_active_edges = 0;
   spans = NULL;

   for (y = yy0; y <= yy1; y++)
     {
        for (; (k < n) && (point[sorted_index[k]].y <= ((double)y + 0.5)); k++)
          {
             i = sorted_index[k];

             if (i > 0) j = i - 1;
             else j = n - 1;
             if (point[j].y <= ((double)y - 0.5))
               {
                  POLY_EDGE_DEL(j)
               }
             else if (point[j].y > ((double)y + 0.5))
               {
                  POLY_EDGE_ADD(j, y)
               }
             if (i < (n - 1)) j = i + 1;
             else j = 0;
             if (point[j].y <= ((double)y - 0.5))
               {
                  POLY_EDGE_DEL(i)
               }
             else if (point[j].y > ((double)y + 0.5))
               {
                  POLY_EDGE_ADD(i, y)
               }
          }

        qsort(edges, num_active_edges, sizeof(RGBA_Edge), polygon_edge_sorter);

        for (j = 0; j < num_active_edges; j += 2)
          {
             int x0, x1;

             x0 = ceil(edges[j].x - 0.5);
             if (j < (num_active_edges - 1))
               x1 = floor(edges[j + 1].x - 0.5);
             else
               x1 = x0;
             if ((x1 >= cx) && (x0 < (cx + cw)) && (x0 <= x1))
               {
                  RGBA_Span *span;

                  if (x0 < cx) x0 = cx;
                  if (x1 >= (cx + cw)) x1 = cx + cw - 1;
                  span = malloc(sizeof(RGBA_Span));
                  spans = eina_inlist_append(spans, EINA_INLIST_GET(span));
                  span->y = y;
                  span->x = x0;
                  span->w = (x1 - x0) + 1;
               }
             edges[j].x += edges[j].dx;
             edges[j + 1].x += edges[j + 1].dx;
          }
     }

   free(edges);
   free(point);
   free(sorted_index);

   evas_common_draw_context_clip_clip(gc->dc, 0, 0, gc->shared->w, gc->shared->h);

   if (spans)
     {
        RGBA_Span *span;

        /* no cutouts - cut right to the chase */
        if (!gc->dc->cutout.rects)
          {
             EINA_INLIST_FOREACH(spans, span)
               {
                  x = span->x;
                  y = span->y;
                  w = span->w;
                  h = 1;
                  evas_gl_common_context_rectangle_push(gc, x, y, w, h,
                                                        cr, cg, cb, ca,
                                                        mtex, mx, my, mw, mh,
                                                        mask_smooth, mask_color);
               }
          }
        else
          {
             /* our clip is 0 size.. abort */
             if ((gc->dc->clip.w > 0) && (gc->dc->clip.h > 0))
               {
                  _evas_gl_common_cutout_rects = evas_common_draw_context_apply_cutouts(gc->dc, _evas_gl_common_cutout_rects);
                  for (i = 0; i < _evas_gl_common_cutout_rects->active; ++i)
                    {
                       r = _evas_gl_common_cutout_rects->rects + i;
                       if ((r->w > 0) && (r->h > 0))
                         {
                            EINA_INLIST_FOREACH(spans, span)
                              {
                                 x = span->x;
                                 y = span->y;
                                 w = span->w;
                                 h = 1;
                                 RECTS_CLIP_TO_RECT(x, y, w, h, r->x, r->y, r->w, r->h);
                                 if ((w > 0) && (h > 0))
                                   evas_gl_common_context_rectangle_push(gc, x, y, w, h,
                                                                         cr, cg, cb, ca,
                                                                         mtex, mx, my, mw, mh,
                                                                         mask_smooth, mask_color);
                              }
                         }
                    }
                  evas_common_draw_context_cutouts_free(_evas_gl_common_cutout_rects);
               }
          }
        while (spans)
          {
             span = (RGBA_Span *)spans;
             spans = eina_inlist_remove(spans, spans);
             free(span);
          }
     }

   /* restore clip info */
   gc->dc->clip.use = c; gc->dc->clip.x = cx; gc->dc->clip.y = cy; gc->dc->clip.w = cw; gc->dc->clip.h = ch;

}
