#include "common.h"

#include <stdlib.h>

#include "blend.h"
#include "image.h"
#include "rgbadraw.h"
#include "span.h"
#include "updates.h"

#define EXCHANGE_POINTS(x0, y0, x1, y1)  \
do {                                     \
	int _tmp = y0;                   \
                                         \
	y0 = y1;                         \
	y1 = _tmp;                       \
                                         \
	_tmp = x0;                       \
	x0 = x1;                         \
	x1 = _tmp;                       \
} while (0)

ImlibUpdate        *
__imlib_Point_DrawToImage(int x, int y, uint32_t color,
                          ImlibImage * im, int clx, int cly, int clw, int clh,
                          ImlibOp op, char blend, char make_updates)
{
   ImlibPointDrawFunction pfunc;

   if (blend && (!A_VAL(&color)))
      return NULL;
   if (!IN_RANGE(x, y, im->w, im->h))
      return NULL;
   if (clw == 0)
     {
        clw = im->w;
        clx = 0;
        clh = im->h;
        cly = 0;
     }
   if (!IN_RECT(x, y, clx, cly, clw, clh))
      return NULL;

   if (A_VAL(&color) == 0xff)
      blend = 0;
   if (blend && IM_FLAG_ISSET(im, F_HAS_ALPHA))
      __imlib_build_pow_lut();

   pfunc = __imlib_GetPointDrawFunction(op, IM_FLAG_ISSET(im, F_HAS_ALPHA),
                                        blend);
   if (pfunc)
      pfunc(color, im->data + (im->w * y) + x);
   if (make_updates)
      return __imlib_AddUpdate(NULL, x, y, 1, 1);
   return NULL;
}

static int
__imlib_SimpleLine_DrawToData(int x0, int y0, int x1, int y1, uint32_t color,
                              uint32_t * dst, int dstw, int clx, int cly,
                              int clw, int clh, int *cl_x0, int *cl_y0,
                              int *cl_x1, int *cl_y1, ImlibOp op,
                              char dst_alpha, char blend)
{
   ImlibPointDrawFunction pfunc;
   ImlibSpanDrawFunction sfunc;
   int                 dx, dy, len, lx, ty, rx, by;
   uint32_t           *p;

   if (A_VAL(&color) == 0xff)
      blend = 0;

   if (y0 > y1)
      EXCHANGE_POINTS(x0, y0, x1, y1);

   dx = x1 - x0;
   dy = y1 - y0;

   lx = clx;
   rx = clx + clw - 1;
   ty = cly;
   by = cly + clh - 1;

   if ((x0 < lx) && (x1 < lx))
      return 0;
   if ((x0 > rx) && (x1 > rx))
      return 0;
   if ((y0 > by) || (y1 < ty))
      return 0;

   if (dy == 0)
     {
        sfunc = __imlib_GetSpanDrawFunction(op, dst_alpha, blend);
        if (!sfunc)
           return 0;

        if (dx < 0)
          {
             int                 tmp = x1;

             x1 = x0;
             x0 = tmp;
          }

        if (x0 < lx)
           x0 = lx;
        if (x1 > rx)
           x1 = rx;

        len = x1 - x0 + 1;

        p = dst + (dstw * y0) + x0;

        sfunc(color, p, len);

        *cl_x0 = x0;
        *cl_y0 = y0;
        *cl_x1 = x1;
        *cl_y1 = y1;

        return 1;
     }

   pfunc = __imlib_GetPointDrawFunction(op, dst_alpha, blend);
   if (!pfunc)
      return 0;

   if (dx == 0)
     {
        if (y0 < ty)
           y0 = ty;
        if (y1 > by)
           y1 = by;

        len = y1 - y0 + 1;

        p = dst + (dstw * y0) + x0;

        while (len--)
          {
             pfunc(color, p);
             p += dstw;
          }

        *cl_x0 = x0;
        *cl_y0 = y0;
        *cl_x1 = x1;
        *cl_y1 = y1;

        return 1;
     }

   if ((dy == dx) || (dy == -dx))
     {
        int                 p0_in, p1_in;

        p0_in = (IN_RECT(x0, y0, clx, cly, clw, clh) ? 1 : 0);
        p1_in = (IN_RECT(x1, y1, clx, cly, clw, clh) ? 1 : 0);

        if (dx > 0)
          {
             if (!p0_in)
               {
                  x0 = x0 + (ty - y0);
                  y0 = ty;
                  if (x0 > rx)
                     return 0;
                  if (x0 < lx)
                    {
                       y0 = y0 + (lx - x0);
                       x0 = lx;
                       if ((y0 < ty) || (y0 > by))
                          return 0;
                    }
               }
             if (!p1_in)
               {
                  x1 = x0 + (by - y0);
                  y1 = by;
                  if (x1 < lx)
                     return 0;
                  if (x1 > rx)
                    {
                       y1 = y0 + (rx - x0);
                       x1 = rx;
                       if ((y1 < ty) || (y1 > by))
                          return 0;
                    }
               }
          }
        else
          {
             if (!p0_in)
               {
                  x0 = x0 - (ty - y0);
                  y0 = ty;
                  if (x0 < lx)
                     return 0;
                  if (x0 > rx)
                    {
                       y0 = y0 - (rx - x0);
                       x0 = rx;
                       if ((y0 < ty) || (y0 > by))
                          return 0;
                    }
               }
             if (!p1_in)
               {
                  x1 = x0 - (by - y0);
                  y1 = by;
                  if (x1 > rx)
                     return 0;
                  if (x1 < lx)
                    {
                       y1 = y0 - (lx - x0);
                       x1 = lx;
                       if ((y1 < ty) || (y1 > by))
                          return 0;
                    }
               }
          }

        len = y1 - y0 + 1;

        p = dst + (dstw * y0) + x0;
        if (dx > 0)
           dstw++;
        else
           dstw--;

        while (len--)
          {
             pfunc(color, p);
             p += dstw;
          }
     }

   *cl_x0 = x0;
   *cl_y0 = y0;
   *cl_x1 = x1;
   *cl_y1 = y1;

   return 1;
}

#define SETUP_LINE_SHALLOW() \
do { \
	if (x0 > x1)                                         \
	  {                                                  \
	   EXCHANGE_POINTS(x0, y0, x1, y1);                  \
	   dx = -dx;                                         \
	   dy = -dy;                                         \
	  }                                                  \
                                                             \
	px = x0;                                             \
	py = y0;                                             \
                                                             \
	p0_in = (IN_RANGE(x0 ,y0 , clw, clh) ? 1 : 0);       \
	p1_in = (IN_RANGE(x1 ,y1 , clw, clh) ? 1 : 0);       \
                                                             \
	dely = 1;                                            \
	dh = dstw;                                           \
	if (dy < 0)                                          \
	  {                                                  \
	   dely = -1;                                        \
	   dh = -dstw;                                       \
	  }                                                  \
                                                             \
	dyy = (dy << 16) / dx;                               \
                                                             \
	if (!p0_in)                                          \
	  {                                                  \
	   dxx = (dx << 16) / dy;                            \
	   if (px < 0)                                       \
	     {                                               \
		x = -px;  px = 0;                            \
		yy = x * dyy;                                \
		y = yy >> 16;                                \
	        if (!a_a)                                    \
                   y += (yy - (y << 16)) >> 15;              \
		py += y;                                     \
		if ((dely > 0) && (py >= clh))               \
		   return 0;                                 \
		else if ((dely < 0) && (py < -1))            \
                   return 0;                                 \
	     }                                               \
                                                             \
	   y = 0;                                            \
	   if ((dely > 0) && (py < -1))                      \
		y = (-1 - py);                               \
	   else if ((dely < 0) && (py >= clh))               \
		y = (clh - 1 - py);                          \
                                                             \
	   xx = y * dxx;                                     \
	   x = xx >> 16;                                     \
	   if (!a_a)                                         \
              x += (xx - (x << 16)) >> 15;                   \
	   px += x;                                          \
	   if (px >= clw) return 0;                          \
                                                             \
	   yy = x * dyy;                                     \
	   y = yy >> 16;                                     \
	   if (!a_a)                                         \
              y += (yy - (y << 16)) >> 15;                   \
	   py += y;                                          \
	   if ((dely > 0) && (py >= clh))                    \
		 return 0;                                   \
	   else if ((dely < 0) && (py < -1))                 \
		 return 0;                                   \
	  }                                                  \
                                                             \
	p = dst + (dstw * py) + px;                          \
                                                             \
	x = px - x0;                                         \
	yy = x * dyy;                                        \
	prev_y = (yy >> 16);                                 \
                                                             \
	rx = MIN(x1 + 1, clw);                               \
	by = clh - 1;                                        \
} while (0)

#define SETUP_LINE_STEEP() \
do { \
   if (y0 > y1)                                              \
     {                                                       \
	EXCHANGE_POINTS(x0, y0, x1, y1);                     \
	dx = -dx;                                            \
	dy = -dy;                                            \
     }                                                       \
                                                             \
   px = x0;                                                  \
   py = y0;                                                  \
                                                             \
   p0_in = (IN_RANGE(x0 ,y0 , clw, clh) ? 1 : 0);            \
   p1_in = (IN_RANGE(x1 ,y1 , clw, clh) ? 1 : 0);            \
                                                             \
   delx = 1;                                                 \
   if (dx < 0)                                               \
	delx = -1;                                           \
                                                             \
   dxx = (dx << 16) / dy;                                    \
                                                             \
   if (!p0_in)                                               \
     {                                                       \
	dyy = (dy << 16) / dx;                               \
                                                             \
	if (py < 0)                                          \
	  {                                                  \
	   y = -py;  py = 0;                                 \
	   xx = y * dxx;                                     \
	   x = xx >> 16;                                     \
	   if (!a_a)                                         \
              x += (xx - (x << 16)) >> 15;                   \
	   px += x;                                          \
	   if ((delx > 0) && (px >= clw))                    \
		return 0;                                    \
	   else if ((delx < 0) && (px < -1))                 \
		return 0;                                    \
	  }                                                  \
                                                             \
	x = 0;                                               \
	if ((delx > 0) && (px < -1))                         \
	   x = (-1 - px);                                    \
	else if ((delx < 0) && (px >= clw))                  \
	   x = (clw - 1 - px);                               \
                                                             \
	yy = x * dyy;                                        \
	y = yy >> 16;                                        \
	if (!a_a)                                            \
           y += (yy - (y << 16)) >> 15;                      \
	py += y;                                             \
	if (py >= clh) return 0;                             \
                                                             \
	xx = y * dxx;                                        \
	x = xx >> 16;                                        \
        if (!a_a)                                            \
           x += (xx - (x << 16)) >> 15;                      \
	px += x;                                             \
	if ((delx > 0) && (px >= clw))                       \
	   return 0;                                         \
	else if ((delx < 0) && (px < -1))                    \
	   return 0;                                         \
     }                                                       \
                                                             \
   p = dst + (dstw * py) + px;                               \
                                                             \
   y = py - y0;                                              \
   xx = y * dxx;                                             \
   prev_x = (xx >> 16);                                      \
                                                             \
   by = MIN(y1 + 1, clh);                                    \
   rx = clw - 1;                                             \
} while (0)

static int
__imlib_Line_DrawToData(int x0, int y0, int x1, int y1, uint32_t color,
                        uint32_t * dst, int dstw, int clx, int cly, int clw,
                        int clh, int *cl_x0, int *cl_y0, int *cl_x1, int *cl_y1,
                        ImlibOp op, char dst_alpha, char blend)
{
   ImlibPointDrawFunction pfunc;
   int                 px, py, x, y, prev_x, prev_y;
   int                 dx, dy, rx, by, p0_in, p1_in, dh, a_a = 0;
   int                 delx, dely, xx, yy, dxx, dyy;
   uint32_t           *p;

   dx = x1 - x0;
   dy = y1 - y0;

   if ((dx == 0) || (dy == 0) || (dx == dy) || (dx == -dy))
      return __imlib_SimpleLine_DrawToData(x0, y0, x1, y1, color,
                                           dst, dstw, clx, cly, clw, clh,
                                           cl_x0, cl_y0, cl_x1, cl_y1,
                                           op, dst_alpha, blend);

   if (A_VAL(&color) == 0xff)
      blend = 0;

   pfunc = __imlib_GetPointDrawFunction(op, dst_alpha, blend);
   if (!pfunc)
      return 0;

   dst += (dstw * cly) + clx;
   x0 -= clx;
   y0 -= cly;
   x1 -= clx;
   y1 -= cly;

   /* shallow: x-parametric */
   if (abs(dy) < abs(dx))
     {
        SETUP_LINE_SHALLOW();

        *cl_x0 = px + clx;
        *cl_y0 = py + cly;

        while (px < rx)
          {
             y = (yy >> 16);
             y += ((yy - (y << 16)) >> 15);
             if (prev_y != y)
               {
                  prev_y = y;
                  p += dh;
                  py += dely;
               }

             if (!p1_in)
               {
                  if ((py < 0) && (dely < 0))
                     break;
                  if ((py > by) && (dely > 0))
                     break;
               }

             if (IN_RANGE(px, py, clw, clh))
                pfunc(color, p);

             yy += dyy;
             px++;
             p++;
          }

        *cl_x1 = px + clx;
        *cl_y1 = py + cly;

        return 1;
     }

   /* steep: y-parametric */

   SETUP_LINE_STEEP();

   *cl_x0 = px + clx;
   *cl_y0 = py + cly;

   while (py < by)
     {
        x = (xx >> 16);
        x += ((xx - (x << 16)) >> 15);
        if (prev_x != x)
          {
             prev_x = x;
             px += delx;
             p += delx;
          }

        if (!p1_in)
          {
             if ((px < 0) && (delx < 0))
                break;
             if ((px > rx) && (delx > 0))
                break;
          }

        if (IN_RANGE(px, py, clw, clh))
           pfunc(color, p);

        xx += dxx;
        py++;
        p += dstw;
     }

   *cl_x1 = px + clx;
   *cl_y1 = py + cly;

   return 1;
}

static int
__imlib_Line_DrawToData_AA(int x0, int y0, int x1, int y1, uint32_t color,
                           uint32_t * dst, int dstw, int clx, int cly, int clw,
                           int clh, int *cl_x0, int *cl_y0, int *cl_x1,
                           int *cl_y1, ImlibOp op, char dst_alpha, char blend)
{
   ImlibPointDrawFunction pfunc;
   int                 px, py, x, y, prev_x, prev_y;
   int                 dx, dy, rx, by, p0_in, p1_in, dh, a_a = 1;
   int                 delx, dely, xx, yy, dxx, dyy;
   uint32_t           *p;
   uint8_t             ca = A_VAL(&color);

   dx = x1 - x0;
   dy = y1 - y0;

   if ((dx == 0) || (dy == 0) || (dx == dy) || (dx == -dy))
      return __imlib_SimpleLine_DrawToData(x0, y0, x1, y1, color,
                                           dst, dstw, clx, cly, clw, clh,
                                           cl_x0, cl_y0, cl_x1, cl_y1,
                                           op, dst_alpha, blend);

   pfunc = __imlib_GetPointDrawFunction(op, dst_alpha, blend);
   if (!pfunc)
      return 0;

   dst += (dstw * cly) + clx;
   x0 -= clx;
   y0 -= cly;
   x1 -= clx;
   y1 -= cly;

   /* shallow: x-parametric */
   if (abs(dy) < abs(dx))
     {
        SETUP_LINE_SHALLOW();

        *cl_x0 = px + clx;
        *cl_y0 = py + cly;

        while (px < rx)
          {
             uint32_t            tmp;
             uint8_t             aa;

             y = (yy >> 16);
             if (prev_y != y)
               {
                  prev_y = y;
                  p += dh;
                  py += dely;
               }

             if (!p1_in)
               {
                  if ((py < -1) && (dely < 0))
                     break;
                  if ((py > by) && (dely > 0))
                     break;
               }

             if ((unsigned)(px) < (unsigned)clw)
               {
                  aa = (yy - (y << 16)) >> 8;

                  A_VAL(&color) = 255 - aa;
                  if (ca < 255)
                     MULT(A_VAL(&color), ca, A_VAL(&color), tmp);

                  if ((unsigned)(py) < (unsigned)clh)
                     pfunc(color, p);

                  if ((unsigned)(py + 1) < (unsigned)clh)
                    {
                       A_VAL(&color) = aa;
                       if (ca < 255)
                          MULT(A_VAL(&color), ca, A_VAL(&color), tmp);
                       pfunc(color, p + dstw);
                    }
               }

             yy += dyy;
             px++;
             p++;
          }

        *cl_x1 = px + clx;
        *cl_y1 = py + cly;

        return 1;
     }

   /* steep: y-parametric */

   SETUP_LINE_STEEP();

   *cl_x0 = px + clx;
   *cl_y0 = py + cly;

   while (py < by)
     {
        uint32_t            tmp;
        uint8_t             aa;

        x = (xx >> 16);
        if (prev_x != x)
          {
             prev_x = x;
             px += delx;
             p += delx;
          }

        if (!p1_in)
          {
             if ((px < -1) && (delx < 0))
                break;
             if ((px > rx) && (delx > 0))
                break;
          }

        if ((unsigned)(py) < (unsigned)clh)
          {
             aa = (xx - (x << 16)) >> 8;

             A_VAL(&color) = 255 - aa;
             if (ca < 255)
                MULT(A_VAL(&color), ca, A_VAL(&color), tmp);

             if ((unsigned)(px) < (unsigned)clw)
                pfunc(color, p);

             if ((unsigned)(px + 1) < (unsigned)clw)
               {
                  A_VAL(&color) = aa;
                  if (ca < 255)
                     MULT(A_VAL(&color), ca, A_VAL(&color), tmp);
                  pfunc(color, p + 1);
               }
          }

        xx += dxx;
        py++;
        p += dstw;
     }

   *cl_x1 = px + clx;
   *cl_y1 = py + cly;

   return 1;
}

ImlibUpdate        *
__imlib_Line_DrawToImage(int x0, int y0, int x1, int y1, uint32_t color,
                         ImlibImage * im, int clx, int cly, int clw, int clh,
                         ImlibOp op, char blend, char anti_alias,
                         char make_updates)
{
   int                 cl_x0, cl_y0, cl_x1, cl_y1, drew;

   if ((x0 == x1) && (y0 == y1))
      return __imlib_Point_DrawToImage(x0, y0, color,
                                       im, clx, cly, clw, clh,
                                       op, blend, make_updates);

   if (blend && (!A_VAL(&color)))
      return NULL;

   if (clw == 0)
     {
        clx = cly = 0;
        clw = im->w;
        clh = im->h;
     }
   else
     {
        CLIP(clx, cly, clw, clh, 0, 0, im->w, im->h);
     }
   if (clw <= 0 || clh <= 0)
      return NULL;

   if ((x0 < clx) && (x1 < clx))
      return NULL;
   if ((x0 >= (clx + clw)) && (x1 >= (clx + clw)))
      return NULL;
   if ((y0 < cly) && (y1 < cly))
      return NULL;
   if ((y0 >= (cly + clh)) && (y1 >= (cly + clh)))
      return NULL;

   if (blend && IM_FLAG_ISSET(im, F_HAS_ALPHA))
      __imlib_build_pow_lut();

   if (anti_alias)
      drew = __imlib_Line_DrawToData_AA(x0, y0, x1, y1, color,
                                        im->data, im->w, clx, cly, clw, clh,
                                        &cl_x0, &cl_y0, &cl_x1, &cl_y1,
                                        op, IM_FLAG_ISSET(im, F_HAS_ALPHA),
                                        blend);
   else
      drew = __imlib_Line_DrawToData(x0, y0, x1, y1, color,
                                     im->data, im->w, clx, cly, clw, clh,
                                     &cl_x0, &cl_y0, &cl_x1, &cl_y1,
                                     op, IM_FLAG_ISSET(im, F_HAS_ALPHA), blend);

   if (drew && make_updates)
     {
        int                 mi, ma, dx, dy, w, h;

        mi = MIN(cl_x0, cl_x1);
        ma = MAX(cl_x0, cl_x1);
        cl_x0 = mi;
        cl_x1 = ma;
        dx = cl_x1 - cl_x0;

        mi = MIN(cl_y0, cl_y1);
        ma = MAX(cl_y0, cl_y1);
        cl_y0 = mi;
        cl_y1 = ma;
        dy = cl_y1 - cl_y0;

        w = dx + 1;
        h = dy + 1;

        if (anti_alias)
          {
             if (((cl_x1 + 1) < (clx + clw)) && (dy > dx))
                w++;
             if (((cl_y1 + 1) < (cly + clh)) && (dx > dy))
                h++;
          }

        CLIP(cl_x0, cl_y0, w, h, clx, cly, clw, clh);
        if (w <= 0 || h <= 0)
           return NULL;

        return __imlib_AddUpdate(NULL, cl_x0, cl_y0, w, h);
     }

   return NULL;
}
