/* Clip line coordinates. Return value:
 * = 0  : No clipping needed.
 * > 0  : Clipped. Line partially inside the clipping area.
 * < 0  : Line completely outside the clipping area.
 */

#ifndef DM_HEADER
#define DM_CLIP_BITS(QB, QX, QY)		\
    do {					\
        QB = 0;					\
        if (QX < clipX0) QB |= CLIP_LEFT;	\
        else					\
        if (QX > clipX1) QB |= CLIP_RIGHT;	\
                                                \
        if (QY < clipY0) QB |= CLIP_TOP;	\
        else					\
        if (QY > clipY1) QB |= CLIP_BOTTOM;	\
    } while (0)

#define xA (fx0)
#define xB (fx1)
#define yA (fy0)
#define yB (fy1)
#endif

#ifdef DM_CLIP_DEBUG
#define CLIPDEB(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define CLIPDEB(fmt, ...)
#endif

int DM_CLIP_FUNC (SDL_Surface *screen, DM_COORD_TYPE *x0, DM_COORD_TYPE *y0, DM_COORD_TYPE *x1, DM_COORD_TYPE *y1)
#ifdef DM_HEADER
;
#else
{
    const DM_COORD_TYPE
        clipX0 = screen->clip_rect.x,
        clipY0 = screen->clip_rect.y,
        clipX1 = clipX0 + screen->clip_rect.w - 1,
        clipY1 = clipY0 + screen->clip_rect.h - 1;
    double fx0 = *x0, fy0 = *y0, fx1 = *x1, fy1 = *y1;
    int clipA, clipB;

    DM_CLIP_BITS(clipA, xA, yA);
    DM_CLIP_BITS(clipB, xB, yB);

    CLIPDEB("-- clip [%1.3f, %1.3f - %1.3f, %1.3f] --\n", xA, yA, xB, yB);

#if 1
    // Cohen-Sutherland clipping method
    do
    {
        const int c = clipA ? clipA : clipB;
        DM_COORD_TYPE x, y;
        
        // The segment is inside?
        if ((clipA | clipB) == 0)
        {
            *x0 = fx0;
            *y0 = fy0;
            *x1 = fx1;
            *y1 = fy1;
            return 0;
        }

        // The line segment is outside of the clip region
        if (clipA & clipB)
            return -1;

#ifdef DM_CLIP_DEBUG
        CLIPDEB    "CLIP   : ");
        if (c == clipA)
            CLIPDEB("A [%1.3f, %1.3f]\n", xA, yA);
        else
            CLIPDEB("B [%1.3f, %1.3f]\n", xB, yB);
#endif
        if (c & CLIP_TOP)
        {
            if (yB - yA != 0)
                x = xA + ((clipY0 - yA) * (xB - xA)) / (yB - yA);
            else
                x = xA;
            y = clipY0;
            CLIPDEB("TOP    : %1.3f, %1.3f\n", x, y);
        }
        else
        if (c & CLIP_BOTTOM)
        {
            if (yB - yA != 0)
                x = xA + ((clipY1 - yA) * (xB - xA)) / (yB - yA);
            else
                x = xA;
            y = clipY1;
            CLIPDEB("BOTTOM : %1.3f, %1.3f\n", x, y);
        }
        else
        if (c & CLIP_RIGHT)
        {
            if (xB - xA != 0)
                y = yA + ((clipX1 - xA) * (yB - yA)) / (xB - xA);
            else
                y = yA;
            x = clipX1;
            CLIPDEB("RIGHT  : %1.3f, %1.3f\n", x, y);
        }
        else
        {
            if (xB - xA != 0)
                y = yA + ((clipX0 - xA) * (yB - yA)) / (xB - xA);
            else
                y = yA;
            x = clipX0;
            CLIPDEB("LEFT   : %1.3f, %1.3f\n", x, y);
        }

        if (c == clipA)
        {
            xA = x;
            yA = y;
            DM_CLIP_BITS(clipA, xA, yA);
        }
        else
        {
            xB = x;
            yB = y;
            DM_CLIP_BITS(clipB, xB, yB);
        }
        
    } while (1);
#else
    // Buyu-Skala clipping method
    const DM_COORD_TYPE dx = xB - xA;
    const DM_COORD_TYPE dy = yB - yA;
    float k, m;
    int z;

    switch (clipA + clipB)
    {
        case 1:
            if (clipA == 1)
            {
                xA = clipX0;
                yA = (clipX0 - xB) * dy / dx + yB;
            }
            else
            {
                xB = clipX0;
                yB = (clipX0 - xA) * dy / dx + yA;
            }
            break;

        case 3:
            k = dy / dx;
            yA = (clipX0 - xA) * k + yA;
            xA = clipX0;
            yB = (clipX1 - xB) * k + yB;
            xB = clipX1;
            break;

        case 5:
            k = dy / dx;
            z = (clipX0 - xA) * k + yA;
            if (z < clipY0)
            {
                switch (clipA)
                {
                    case 0:
                        xB = xB + (clipY0 - yB) / k;
                        yB = clipY0;
                        break;
                    case 5:
                        xA = xA + (clipY0 - yA) / k;
                        yA = clipY0;
                        break;

                    default:
                        return -1;  /* the line segment is outside */
                }
            }
            else
            {
                switch (clipA)
                {
                    case 0:
                        xB = clipX0;
                        yB = z;
                        break;
                    case 1:
                        xB = xB + (clipY0 - yB) / k;
                        yB = clipY0;
                        xA = clipX0;
                        yA = z;
                        break;
                    case 4:
                        xA = xA + (clipY0 - yA) / k;
                        yA = clipY0;
                        xB = clipX0;
                        yB = z;
                        break;
                    case 5:
                        xA = clipX0;
                        yA = z;
                        break;
                }
            }
            break;

        case 7:
            switch (clipA)
            {
                case 1:
                    k = dy / dx;
                    yA = (clipX0 - xB) * k + yB;
                    if (yA < clipY0)
                        return -1;  /* the line segment is outside */
                    xA = clipX0;
                    yB = (clipX1 - clipX0) * k + yA;
                    if (yB < clipY0)
                    {
                        xB = (clipY0 - yB) / k + clipX1;
                        yB = clipY0;
                    }
                    else
                        xB = clipX1;
                    break;

                    /* similarly for cases clipA == 2, 5, 6 */
            }
        case 15:
            switch (clipA)
            {
            case 5:
                if (dy * (clipX1 - clipX0) < dx * (clipY1 - clipY0))
                {
                    k = dy / dx;
                    yA = (clipX0 - xB) * k + yB;
                    if (yA > clipY1)
                        return -1;  /* the line segment is outside */
                    yB = (clipX1 - clipX0) * k + yA;
                    if (yB < clipY0)
                        return -1;  /* the line segment is outside */
                    if (yA < clipY0)
                    {
                        xA = (clipY0 - yA) / k + clipX0;
                        yA = clipY0;
                        xB = clipX1;
                    }
                    else
                    {
                        xA = clipX0;
                        if (yB > clipY1)
                        {
                            xB = (clipY1 - yB) / k + clipX1;
                            yB = clipY1;
                        }
                        else
                            xB = clipX1;
                    }
                }
                else
                {
                    m = dx / dy;
                    xA = (clipY0 - yB) * m + xB;
                    if (xA > clipX1)
                        return -1;  /* the line segment is outside */
                    xB = (clipY1 - clipY0) * m + xA;
                    if (xB < clipX0)
                        return -1;  /* the line segment is outside */
                    if (xA < clipX0)
                    {
                        yA = (clipX0 - xA) / m + clipY0;
                        xA = clipX0;
                        yB = clipY1;
                    }
                    else
                    {
                        yA = clipY0;
                        if (xB > clipX1)
                        {
                            yB = (clipX1 - xB) / m + clipY1;
                            xB = clipX1;
                        }
                        else
                            yB = clipY1;
                    }
                }

                /* similarly for cases clipA == 6, 9, 10 */
            }

            /* cases 2, 4, 8 are similar as case 1, cases 6, 9, 10 are similar as case 5 */
            /* cases 11, 13, 14 are similar as case 7, case 12 is similar case 3 */
    }                           /* of case clipA + clipB */
#endif

    return 1;
}

#undef DM_CLIP_BITS
#undef xA
#undef xB
#undef yA
#undef yB
#undef CLIPDEB
#endif

#undef DM_CLIP_FUNC
#undef DM_COORD_TYPE
