#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->has_alpha)
        __imlib_build_pow_lut();

    pfunc = __imlib_GetPointDrawFunction(op, im->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->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->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->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;
}
