#include "common.h"

#include <stdlib.h>

#include "asm_c.h"
#include "blend.h"
#include "image.h"
#include "rotate.h"

/*\ Linear interpolation functions \*/
/*\ Between two values \*/
#define INTERP(v1, v2, f) \
    (((v1) << _ROTATE_PREC) + (((v2) - (v1)) * (f)))

/*\ Between two colour bytes \*/
#define INTERP_VAL1(x_VAL, dest, l, r, x) \
    x_VAL(dest) = (INTERP(x_VAL(l), x_VAL(r), (x)) >> _ROTATE_PREC)

/*\ Alpha channel: between two values and two zeroes \*/
#define INTERP_VAL1_A0(dest, v1, v2, f1, f2) \
    A_VAL(dest) = ((INTERP(A_VAL(v1), A_VAL(v2), (f1)) * \
                   (f2)) >> (2 * _ROTATE_PREC))

/*\ Between four values \*/
#define INTERP_VAL2(x_VAL, dest, ul, ur, ll, lr, x, y) \
    x_VAL(dest) = (INTERP(INTERP(x_VAL(ul), x_VAL(ur), (x)),    \
                  INTERP(x_VAL(ll), x_VAL(lr), (x)),    \
                 (y)) >> (2 * _ROTATE_PREC))

/*\ Functions used in rotation routines.
|*| The do { } while(0) construction is to make it one statement.
\*/
/*\ Between four colours \*/
#define INTERP_ARGB(dest, src, sow, x, y) do { \
    INTERP_VAL2(R_VAL, (dest), (src), (src) + 1, (src) + (sow), (src) + (sow) + 1, (x) & _ROTATE_PREC_BITS, (y) & _ROTATE_PREC_BITS);   \
    INTERP_VAL2(G_VAL, (dest), (src), (src) + 1, (src) + (sow), (src) + (sow) + 1, (x) & _ROTATE_PREC_BITS, (y) & _ROTATE_PREC_BITS);   \
    INTERP_VAL2(B_VAL, (dest), (src), (src) + 1, (src) + (sow), (src) + (sow) + 1, (x) & _ROTATE_PREC_BITS, (y) & _ROTATE_PREC_BITS);   \
    INTERP_VAL2(A_VAL, (dest), (src), (src) + 1, (src) + (sow), (src) + (sow) + 1, (x) & _ROTATE_PREC_BITS, (y) & _ROTATE_PREC_BITS);   \
} while (0)

/*\ Between two colours, alpha between two values and zeroes \*/
#define INTERP_RGB_A0(dest, v1, v2, f, f2) do { \
    INTERP_VAL1(R_VAL, (dest), (v1), (v2), (f) & _ROTATE_PREC_BITS); \
    INTERP_VAL1(G_VAL, (dest), (v1), (v2), (f) & _ROTATE_PREC_BITS); \
    INTERP_VAL1(B_VAL, (dest), (v1), (v2), (f) & _ROTATE_PREC_BITS); \
    INTERP_VAL1_A0(dest, (v1), (v2), (f) & _ROTATE_PREC_BITS, (f2) & _ROTATE_PREC_BITS); \
} while (0)

/*\ One colour, alpha between one value and three zeroes \*/
#define INTERP_A000(dest, v, f1, f2) do { \
    *(dest) = *(v);             \
    A_VAL(dest) = (A_VAL(v) *   \
        ((f1) & _ROTATE_PREC_BITS) * ((f2) & _ROTATE_PREC_BITS)) >> (2 * _ROTATE_PREC); \
} while (0)

/*\ Rotate by pixel sampling only, target inside source \*/
static void
__imlib_RotateSampleInside(uint32_t *src, uint32_t *dest, int sow, int dow,
                           int dw, int dh, int x, int y,
                           int dxh, int dyh, int dxv, int dyv)
{
    int             i;

    if ((dw < 1) || (dh < 1))
        return;

    while (1)
    {
        i = dw - 1;
        do
        {
            *dest = src[(x >> _ROTATE_PREC) + ((y >> _ROTATE_PREC) * sow)];
            /*\ RIGHT; \ */
            x += dxh;
            y += dyh;
            dest++;
        }
        while (--i >= 0);
        if (--dh <= 0)
            break;
        /*\ DOWN/LEFT; \ */
        x += dxv - dw * dxh;
        y += dyv - dw * dyh;
        dest += (dow - dw);
    }
}

/*\ Same as last function, but with antialiasing \*/
static void
__imlib_RotateAAInside(uint32_t *src, uint32_t *dest, int sow, int dow,
                       int dw, int dh, int x, int y,
                       int dxh, int dyh, int dxv, int dyv)
{
    int             i;

    if ((dw < 1) || (dh < 1))
        return;

    while (1)
    {
        i = dw - 1;
        do
        {
            uint32_t       *src_x_y = (src + (x >> _ROTATE_PREC) +
                                       ((y >> _ROTATE_PREC) * sow));
            INTERP_ARGB(dest, src_x_y, sow, x, y);
            /*\ RIGHT; \ */
            x += dxh;
            y += dyh;
            dest++;
        }
        while (--i >= 0);
        if (--dh <= 0)
            break;
        /*\ DOWN/LEFT; \ */
        x += dxv - dw * dxh;
        y += dyv - dw * dyh;
        dest += (dow - dw);
    }
}

/*\ NOTE: To check if v is in [b .. t) ((v >= b) && (v < t))
|*|  it's quicker to do ((unsigned)(v - b) < (t - b))
|*|  as negative values, cast to unsigned, become large positive
|*|  values, and fall through the compare.
|*|  v in [0 .. t) is a special case: ((unsigned)v < t)
|*|  v in [-t .. 0) is also special, as its the same as ~v in [0 .. t)
\*/
static int
__check_inside_coords(int x, int y, int dxh, int dyh, int dxv, int dyv,
                      int dw, int dh, int sw, int sh)
{
    sw <<= _ROTATE_PREC;
    sh <<= _ROTATE_PREC;

    if (((unsigned)x >= (unsigned)sw) || ((unsigned)y >= (unsigned)sh))
        return 0;
    x += dxh * dw;
    y += dyh * dw;
    if (((unsigned)x >= (unsigned)sw) || ((unsigned)y >= (unsigned)sh))
        return 0;
    x += dxv * dh;
    y += dyv * dh;
    if (((unsigned)x >= (unsigned)sw) || ((unsigned)y >= (unsigned)sh))
        return 0;
    x -= dxh * dw;
    y -= dyh * dw;
    if (((unsigned)x >= (unsigned)sw) || ((unsigned)y >= (unsigned)sh))
        return 0;

    return 1;
}

/*\ These ones don't need the target to be inside the source \*/
void
__imlib_RotateSample(uint32_t *src, uint32_t *dest, int sow, int sw, int sh,
                     int dow, int dw, int dh, int x, int y,
                     int dxh, int dyh, int dxv, int dyv)
{
    int             i;

    if ((dw < 1) || (dh < 1))
        return;

    if (__check_inside_coords(x, y, dxh, dyh, dxv, dyv, dw, dh, sw, sh))
    {
        __imlib_RotateSampleInside(src, dest, sow, dow, dw, dh, x, y,
                                   dxh, dyh, dxv, dyv);
        return;

    }

    sw <<= _ROTATE_PREC;
    sh <<= _ROTATE_PREC;
    while (1)
    {
        i = dw - 1;
        do
        {
            if (((unsigned)x < (unsigned)sw) && ((unsigned)y < (unsigned)sh))
                *dest = src[(x >> _ROTATE_PREC) + ((y >> _ROTATE_PREC) * sow)];
            else
                *dest = 0;
            /*\ RIGHT; \ */
            x += dxh;
            y += dyh;
            dest++;

        }
        while (--i >= 0);
        if (--dh <= 0)
            break;
        /*\ DOWN/LEFT; \ */
        x += dxv - dw * dxh;
        y += dyv - dw * dyh;
        dest += (dow - dw);

    }
}

/*\ With antialiasing.
|*| NB: The function 'sees' a transparent border around the source,
|*|     with colour channels matching the edge, so there is no need to do
|*|     anything special, but remember to account for this when calculating
|*|     the bounding box.
\*/
void
__imlib_RotateAA(uint32_t *src, uint32_t *dest, int sow, int sw, int sh,
                 int dow, int dw, int dh, int x, int y,
                 int dxh, int dyh, int dxv, int dyv)
{
    int             i;

    if ((dw < 1) || (dh < 1))
        return;

#ifdef DO_MMX_ASM
    if (__imlib_do_asm())
    {
        __imlib_mmx_RotateAA(src, dest, sow, sw, sh, dow, dw, dh, x, y,
                             dxh, dyh, dxv, dyv);
        return;
    }
#endif

    if (__check_inside_coords(x, y, dxh, dyh, dxv, dyv, dw, dh, sw - 1, sh - 1))
    {
        __imlib_RotateAAInside(src, dest, sow, dow, dw, dh, x, y,
                               dxh, dyh, dxv, dyv);
        return;

    }

    sw--;
    sh--;
    sw <<= _ROTATE_PREC;
    sh <<= _ROTATE_PREC;
    while (1)
    {
        i = dw - 1;
        do
        {
            uint32_t       *src_x_y = (src + (x >> _ROTATE_PREC) +
                                       ((y >> _ROTATE_PREC) * sow));
            if ((unsigned)x < (unsigned)sw)
            {
                if ((unsigned)y < (unsigned)sh)
                {
                    /*\  12
                     * |*|  34
                     * \ */
                    INTERP_ARGB(dest, src_x_y, sow, x, y);
                }
                else if ((unsigned)(y - sh) < _ROTATE_PREC_MAX)
                {
                    /*\  12
                     * |*|  ..
                     * \ */
                    INTERP_RGB_A0(dest, src_x_y, src_x_y + 1, x, ~y);
                }
                else if ((unsigned)(~y) < _ROTATE_PREC_MAX)
                {
                    /*\  ..
                     * |*|  34
                     * \ */
                    INTERP_RGB_A0(dest, src_x_y + sow, src_x_y + sow + 1, x, y);
                }
                else
                    *dest = 0;
            }
            else if ((unsigned)(x - sw) < (_ROTATE_PREC_MAX))
            {
                if ((unsigned)y < (unsigned)sh)
                {
                    /*\  1.
                     * |*|  3.
                     * \ */
                    INTERP_RGB_A0(dest, src_x_y, src_x_y + sow, y, ~x);
                }
                else if ((unsigned)(y - sh) < _ROTATE_PREC_MAX)
                {
                    /*\  1.
                     * |*|  ..
                     * \ */
                    INTERP_A000(dest, src_x_y, ~x, ~y);
                }
                else if ((unsigned)(~y) < _ROTATE_PREC_MAX)
                {
                    /*\  ..
                     * |*|  3.
                     * \ */
                    INTERP_A000(dest, src_x_y + sow, ~x, y);
                }
                else
                    *dest = 0;
            }
            else if ((unsigned)(~x) < _ROTATE_PREC_MAX)
            {
                if ((unsigned)y < (unsigned)sh)
                {
                    /*\  .2
                     * |*|  .4
                     * \ */
                    INTERP_RGB_A0(dest, src_x_y + 1, src_x_y + sow + 1, y, x);
                }
                else if ((unsigned)(y - sh) < _ROTATE_PREC_MAX)
                {
                    /*\  .2
                     * |*|  ..
                     * \ */
                    INTERP_A000(dest, src_x_y + 1, x, ~y);
                }
                else if ((unsigned)(~y) < _ROTATE_PREC_MAX)
                {
                    /*\  ..
                     * |*|  .4
                     * \ */
                    INTERP_A000(dest, src_x_y + sow + 1, x, y);
                }
                else
                    *dest = 0;
            }
            else
                *dest = 0;
            /*\ RIGHT; \ */
            x += dxh;
            y += dyh;
            dest++;

        }
        while (--i >= 0);
        if (--dh <= 0)
            break;
        /*\ DOWN/LEFT; \ */
        x += dxv - dw * dxh;
        y += dyv - dw * dyh;
        dest += (dow - dw);

    }
}

/*\ Should this be in blend.c ?? \*/
#define LINESIZE 16

void
__imlib_BlendImageToImageSkewed(ImlibImage *im_src, ImlibImage *im_dst,
                                char aa, char blend, char merge_alpha,
                                int ssx, int ssy, int ssw, int ssh,
                                int ddx, int ddy,
                                int hsx, int hsy, int vsx, int vsy,
                                ImlibColorModifier *cm, ImlibOp op,
                                int clx, int cly, int clw, int clh)
{
    int             x, y, dxh, dyh, dxv, dyv, i;
    double          xy2;
    uint32_t       *data, *src;

    /* Image data should(must) be loaded here but let's just check anyway */
    if (!im_src->data || !im_dst->data)
        return;

    if ((ssw < 0) || (ssh < 0))
        return;

    /*\ Complicated gonio.  Works on paper..
     * |*| Too bad it doesn't all fit into integer math.. 
     * \ */
    if (vsx | vsy)
    {
        xy2 = (double)(hsx * vsy - vsx * hsy) / _ROTATE_PREC_MAX;
        if (xy2 == 0.0)
            return;
        dxh = (double)(ssw * vsy) / xy2;
        dxv = (double)-(ssw * vsx) / xy2;
        dyh = (double)-(ssh * hsy) / xy2;
        dyv = (double)(ssh * hsx) / xy2;
    }
    else
    {
        xy2 = (double)(hsx * hsx + hsy * hsy) / _ROTATE_PREC_MAX;
        if (xy2 == 0.0)
            return;
        dxh = (double)(ssw * hsx) / xy2;
        dyh = (double)-(ssw * hsy) / xy2;
        dxv = -dyh;
        dyv = dxh;
    }
    x = -(ddx * dxh + ddy * dxv);
    y = -(ddx * dyh + ddy * dyv);

    if (ssx < 0)
    {
        x += ssx * _ROTATE_PREC_MAX;
        ssw += ssx;
        ssx = 0;

    }
    if (ssy < 0)
    {
        y += ssy * _ROTATE_PREC_MAX;
        ssh += ssy;
        ssy = 0;

    }
    if ((ssw + ssx) > im_src->w)
        ssw = im_src->w - ssx;
    if ((ssh + ssy) > im_src->h)
        ssh = im_src->h - ssy;

    src = im_src->data + ssx + ssy * im_src->w;
    data = malloc(im_dst->w * LINESIZE * sizeof(uint32_t));
    if (!data)
        return;

    if (aa)
    {
        /*\ Account for virtual transparent border \ */
        x += _ROTATE_PREC_MAX;
        y += _ROTATE_PREC_MAX;
    }

    for (i = 0; i < im_dst->h; i += LINESIZE)
    {
        int             x2, y2, w, h, l, r;

        h = MIN(LINESIZE, im_dst->h - i);

        x2 = x + h * dxv;
        y2 = y + h * dyv;

        w = ssw << _ROTATE_PREC;
        h = ssh << _ROTATE_PREC;
        if (aa)
        {
            /*\ Account for virtual transparent border \ */
            w += 2 << _ROTATE_PREC;
            h += 2 << _ROTATE_PREC;
        }
        /*\ Pretty similar code \ */
        if (dxh > 0)
        {
            if (dyh > 0)
            {
                l = MAX(-MAX(y, y2) / dyh, -MAX(x, x2) / dxh);
                r = MIN((h - MIN(y, y2)) / dyh, (w - MIN(x, x2)) / dxh);

            }
            else if (dyh < 0)
            {
                l = MAX(-MAX(x, x2) / dxh, (h - MIN(y, y2)) / dyh);
                r = MIN(-MAX(y, y2) / dyh, (w - MIN(x, x2)) / dxh);

            }
            else
            {
                l = -MAX(x, x2) / dxh;
                r = (w - MIN(x, x2)) / dxh;

            }
        }
        else if (dxh < 0)
        {
            if (dyh > 0)
            {
                l = MAX(-MAX(y, y2) / dyh, (w - MIN(x, x2)) / dxh);
                r = MIN(-MAX(x, x2) / dxh, (h - MIN(y, y2)) / dyh);

            }
            else if (dyh < 0)
            {
                l = MAX((h - MIN(y, y2)) / dyh, (w - MIN(x, x2)) / dxh);
                r = MIN(-MAX(y, y2) / dyh, -MAX(x, x2) / dxh);

            }
            else
            {
                l = (w - MIN(x, x2)) / dxh;
                r = -MAX(x, x2) / dxh;

            }

        }
        else
        {
            if (dyh > 0)
            {
                l = -MAX(y, y2) / dyh;
                r = (h - MIN(y, y2)) / dyh;

            }
            else if (dyh < 0)
            {
                l = (h - MIN(y, y2)) / dyh;
                r = -MAX(y, y2) / dyh;

            }
            else
            {
                l = 0;
                r = 0;

            }

        }
        l--;
        r += 2;                 /*\ Be paranoid about roundoff errors \ */
        if (l < 0)
            l = 0;
        if (r > im_dst->w)
            r = im_dst->w;
        if (r <= l)
        {
            x = x2;
            y = y2;
            continue;

        }

        w = r - l;
        h = MIN(LINESIZE, im_dst->h - i);
        x += l * dxh;
        y += l * dyh;
        if (aa)
        {
            x -= _ROTATE_PREC_MAX;
            y -= _ROTATE_PREC_MAX;
            __imlib_RotateAA(src, data, im_src->w, ssw, ssh, w, w, h,
                             x, y, dxh, dyh, dxv, dyv);

        }
        else
        {
            __imlib_RotateSample(src, data, im_src->w, ssw, ssh, w, w, h,
                                 x, y, dxh, dyh, dxv, dyv);

        }
        __imlib_BlendRGBAToData(data, w, h, im_dst->data,
                                im_dst->w, im_dst->h, 0, 0, l, i, w, h,
                                blend, merge_alpha, cm, op, 0);
        x = x2;
        y = y2;

    }
    free(data);
}
