/*
 *
 *
 * Copyright 1982 by Digital Research Inc.  All rights reserved.
 * Copyright 1999 by Caldera, Inc. and Authors:
 * Copyright 2002-2013 The EmuTOS development team
 *
 * This file is distributed under the GPL, version 2 or at your
 * option any later version.  See doc/license.txt for details.
 */



#include "config.h"
#include "portab.h"
#include "vdi_defs.h"
#include "tosvars.h"
#include "lineavars.h"

#define EMPTY   0xffff
#define DOWN_FLAG 0x8000
#define QSIZE 200
#define QMAX QSIZE-1



#define ABS(v) (v & 0x7FFF)



/* prototypes */
static void crunch_queue(void);
static BOOL clipbox(Vwk * vwk, Rect * rect);



/* Global variables */
static UWORD search_color;       /* the color of the border      */


/* some kind of stack for the segments to fill */
static WORD queue[QSIZE];       /* storage for the seed points  */
static WORD qbottom;            /* the bottom of the queue (zero)   */
static WORD qtop;               /* points top seed +3           */
static WORD qptr;               /* points to the active point   */
static WORD qtmp;
static WORD qhole;              /* an empty space in the queue */


/* the storage for the used defined fill pattern */
const UWORD ROM_UD_PATRN[16] = {
    0x07E0, 0x0FF0, 0x1FD8, 0x1808, 0x1808, 0x1008, 0x1E78, 0x1348,
    0x1108, 0x0810, 0x0B70, 0x0650, 0x07A0, 0x1E20, 0x1BC0, 0x1800
};

static const UWORD OEMMSKPAT = 7;
static const UWORD OEMPAT[128] = {
    /* Brick */
    0xFFFF, 0x8080, 0x8080, 0x8080, 0xFFFF, 0x0808, 0x0808, 0x0808,
    /* Diagonal Bricks */
    0x2020, 0x4040, 0x8080, 0x4141, 0x2222, 0x1414, 0x0808, 0x1010,
    /* Grass */
    0x0000, 0x0000, 0x1010, 0x2828, 0x0000, 0x0000, 0x0101, 0x8282,
    /* Trees */
    0x0202, 0x0202, 0xAAAA, 0x5050, 0x2020, 0x2020, 0xAAAA, 0x0505,
    /* Dashed x's */
    0x4040, 0x8080, 0x0000, 0x0808, 0x0404, 0x0202, 0x0000, 0x2020,
    /* Cobble Stones */
    0x6606, 0xC6C6, 0xD8D8, 0x1818, 0x8181, 0x8DB1, 0x0C33, 0x6000,
    /* Sand */
    0x0000, 0x0000, 0x0400, 0x0000, 0x0010, 0x0000, 0x8000, 0x0000,
    /* Rough Weave */
    0xF8F8, 0x6C6C, 0xC6C6, 0x8F8F, 0x1F1F, 0x3636, 0x6363, 0xF1F1,
    /* Quilt */
    0xAAAA, 0x0000, 0x8888, 0x1414, 0x2222, 0x4141, 0x8888, 0x0000,
    /* Paterned Cross */
    0x0808, 0x0000, 0xAAAA, 0x0000, 0x0808, 0x0000, 0x8888, 0x0000,
    /* Balls */
    0x7777, 0x9898, 0xF8F8, 0xF8F8, 0x7777, 0x8989, 0x8F8F, 0x8F8F,
    /* Verticle Scales */
    0x8080, 0x8080, 0x4141, 0x3E3E, 0x0808, 0x0808, 0x1414, 0xE3E3,
    /* Diagonal scales */
    0x8181, 0x4242, 0x2424, 0x1818, 0x0606, 0x0101, 0x8080, 0x8080,
    /* Checker Board */
    0xF0F0, 0xF0F0, 0xF0F0, 0xF0F0, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F,
    /* Filled Diamond */
    0x0808, 0x1C1C, 0x3E3E, 0x7F7F, 0xFFFF, 0x7F7F, 0x3E3E, 0x1C1C,
    /* Herringbone */
    0x1111, 0x2222, 0x4444, 0xFFFF, 0x8888, 0x4444, 0x2222, 0xFFFF
};

static const UWORD DITHRMSK = 3;              /* mask off all but four scans */
static const UWORD DITHER[32] = {
    0x0000, 0x4444, 0x0000, 0x1111,     /* intensity level 2 */
    0x0000, 0x5555, 0x0000, 0x5555,     /* intensity level 4 */
    0x8888, 0x5555, 0x2222, 0x5555,     /* intensity level 6 */
    0xAAAA, 0x5555, 0xAAAA, 0x5555,     /* intensity level 8 */
    0xAAAA, 0xDDDD, 0xAAAA, 0x7777,     /* intensity level 10 */
    0xAAAA, 0xFFFF, 0xAAAA, 0xFFFF,     /* intensity level 12 */
    0xEEEE, 0xFFFF, 0xBBBB, 0xFFFF,     /* intensity level 14 */
    0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF      /* intensity level 16 */
};

static const UWORD HAT_0_MSK = 7;
static const UWORD HATCH0[48] = {
    /* narrow spaced + 45 */
    0x0101, 0x0202, 0x0404, 0x0808, 0x1010, 0x2020, 0x4040, 0x8080,
    /* medium spaced thick 45 deg */
    0x6060, 0xC0C0, 0x8181, 0x0303, 0x0606, 0x0C0C, 0x1818, 0x3030,
    /* medium +-45 deg */
    0x4242, 0x8181, 0x8181, 0x4242, 0x2424, 0x1818, 0x1818, 0x2424,
    /* medium spaced vertical */
    0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080,
    /* medium spaced horizontal */
    0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    /* medium spaced cross */
    0xFFFF, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080
};

static const UWORD HAT_1_MSK = 0xF;
static const UWORD HATCH1[96] = {
    /* wide +45 deg */
    0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
    0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000, 0x8000,
    /* widely spaced thick 45 deg */
    0x8003, 0x0007, 0x000E, 0x001C, 0x0038, 0x0070, 0x00E0, 0x01C0,
    0x0380, 0x0700, 0x0E00, 0x1C00, 0x3800, 0x7000, 0x0E000, 0x0C001,
    /* widely +- 45 deg */
    0x8001, 0x4002, 0x2004, 0x1008, 0x0810, 0x0420, 0x0240, 0x0180,
    0x0180, 0x0240, 0x0420, 0x0810, 0x1008, 0x2004, 0x4002, 0x8001,
    /* widely spaced vertical */
    0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000,
    0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000,
    /* widely spaced horizontal */
    0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    /* widely spaced horizontal/vert cross */
    0xFFFF, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080,
    0xFFFF, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080,
};

const UWORD HOLLOW = 0;
const UWORD SOLID = 0xFFFF;



/*
 * dsf_udpat - Update pattern
 */

void
dsf_udpat(Vwk * vwk)
{
    WORD *sp, *dp, i, count;

    count = CONTRL[3];

    if (count == 16)
        vwk->multifill = 0;        /* Single Plane Pattern */
    else if (count == (INQ_TAB[4] * 16))
        vwk->multifill = 1;        /* Valid Multi-plane pattern */
    else
        return;             /* Invalid pattern, return */

    sp = INTIN;
    dp = &vwk->ud_patrn[0];
    for (i = 0; i < count; i++)
        *dp++ = *sp++;
}



/*
 * _vsf_interior - Set fill style
 */

void
_vsf_interior(Vwk * vwk)
{
    WORD fs;

    CONTRL[4] = 1;
    fs = *INTIN;
    if ((fs > MX_FIL_STYLE) || (fs < 0))
        fs = 0;
    *INTOUT = vwk->fill_style = fs;
    st_fl_ptr(vwk);
}



/* S_FILL_INDEX: */
void
_vsf_style(Vwk * vwk)
{
    WORD fi;

    CONTRL[4] = 1;
    fi = *INTIN;

    if (vwk->fill_style == 2) {
        if ((fi > MX_FIL_PAT_INDEX) || (fi < 1))
            fi = 1;
    } else {
        if ((fi > MX_FIL_HAT_INDEX) || (fi < 1))
            fi = 1;
    }
    vwk->fill_index = (*INTOUT = fi) - 1;
    st_fl_ptr(vwk);
}



/* S_FILL_COLOR: */
void
_vsf_color(Vwk * vwk)
{
    WORD fc;

    *(CONTRL + 4) = 1;
    fc = *INTIN;
    if ((fc >= DEV_TAB[13]) || (fc < 0))
        fc = 1;

    *INTOUT = fc;
    vwk->fill_color = MAP_COL[fc];
}



/* ST_FILLPERIMETER: */
void
_vsf_perimeter(Vwk * vwk)
{
    WORD *int_out;

    int_out = INTOUT;

    if (*INTIN == 0) {
        *int_out = 0;
        vwk->fill_per = FALSE;
    } else {
        *(int_out) = 1;
        vwk->fill_per = TRUE;
    }
    CONTRL[4] = 1;
}


/*
 * dr_recfl - draw filled rectangle
 */

void
dr_recfl(Vwk * vwk)
{
    Rect * rect = (Rect*)PTSIN;

    if (vwk->clip)
        if (!clipbox(vwk, rect))
            return;

    /* do the real work... */
    draw_rect(vwk, rect, vwk->fill_color);
}



/*
 * _v_cellarray - Draw a square of sqares (just color devices)
 */
void
_v_cellarray(Vwk * vwk)
{
    /* not implemented */
}



/*
 * _vq_cellarray -
 */
void
_vq_cellarray(Vwk * vwk)
{
    /* not implemented */
}



/*
 * vql_attr - Inquire current fill area attributes
 */

void
vqf_attr(Vwk * vwk)
{
    WORD *pointer;

    pointer = INTOUT;
    *pointer++ = vwk->fill_style;
    *pointer++ = REV_MAP_COL[vwk->fill_color];
    *pointer++ = vwk->fill_index + 1;
    *pointer++ = vwk->wrt_mode + 1;
    *pointer = vwk->fill_per;

    CONTRL[4] = 5;
}



/*
 * st_fl_ptr - set fill pattern?
 */

void
st_fl_ptr(Vwk * vwk)
{
    WORD fi, pm;
    const UWORD *pp = NULL;

    fi = vwk->fill_index;
    pm = 0;
    switch (vwk->fill_style) {
    case 0:
        pp = &HOLLOW;
        break;

    case 1:
        pp = &SOLID;
        break;

    case 2:
        if (fi < 8) {
            pm = DITHRMSK;
            pp = &DITHER[fi * (pm + 1)];
        } else {
            pm = OEMMSKPAT;
            pp = &OEMPAT[(fi - 8) * (pm + 1)];
        }
        break;
    case 3:
        if (fi < 6) {
            pm = HAT_0_MSK;
            pp = &HATCH0[fi * (pm + 1)];
        } else {
            pm = HAT_1_MSK;
            pp = &HATCH1[(fi - 6) * (pm + 1)];
        }
        break;
    case 4:
        pm = 0x000f;
        pp = (UWORD *)&vwk->ud_patrn[0];
        break;
    }
    vwk->patptr = (UWORD *)pp;
    vwk->patmsk = pm;
}



/*
 * bub_sort - sorts an array of words
 *
 * This routine bubble-sorts an array of words into ascending order.
 *
 * input:
 *     buf   - ptr to start of array.
 *     count - number of words in array.
 */

static void
bub_sort (WORD * buf, WORD count)
{
    int i, j;

    for (i = count-1; i > 0; i--) {
        WORD * ptr = buf;               /* reset pointer to the array */
        for (j = 0; j < i; j++) {
            WORD val = *ptr++;   /* word */    /* get next value */
            if ( val > *ptr ) {    /* yes - do nothing */
                *(ptr-1) = *ptr;   /* word */    /* nope - swap them */
                *ptr = val;   /* word */
            }
        }
    }
}



/*
 * clc_flit - draw a filled polygon
 *
 * (Sutherland and Hodgman Polygon Clipping Algorithm)
 *
 * For each non-horizontal scanline crossing poly, do:
 *   - find intersection points of scan line with poly edges.
 *   - Sort intersections left to right
 *   - Draw pixels between each pair of points (x coords) on the scan line
 */
/*
 * the buffer used by clc_flit() has been temporarily moved from the
 * stack to a local static area.  this avoids some cases of stack
 * overflow when the VDI is called from the AES (and the stack is the
 * small one located in the UDA).  this fix allows GemAmigo to run.
 *
 * this change restores the situation that existed in the original
 * DRI code, when clc_flit() was written in assembler; the buffer
 * was moved to the stack when clc_flit() was re-implemented in C.
 */
#define MAX_INTERSECTIONS   256
static WORD fill_buffer[MAX_INTERSECTIONS];

void
clc_flit (const VwkAttrib * attr, const VwkClip * clipper, const Point * point, WORD y, int vectors)
{
//    WORD fill_buffer[256];      /* must be 256 words or it will fail */
    WORD * bufptr;              /* point to array of x-values. */
    int intersections;          /* count of intersections */
    int i;

    /* Initialize the pointers and counters. */
    intersections = 0;  /* reset counter */
    bufptr = fill_buffer;

    /* find intersection points of scan line with poly edges. */
    for (i = vectors - 1; i >= 0; i--) {
        WORD x1, x2, y1, y2, dy;

        x1 = point->x;          /* fetch x-value of 1st endpoint. */
        y1 = point->y;          /* fetch y-value of 1st endpoint. */
        point++;
        x2 = point->x;          /* fetch x-value of 2nd endpoint. */
        y2 = point->y;          /* fetch y-value of 2nd endpoint. */

        /* if the current vector is horizontal, ignore it. */
        dy = y2 - y1;
        if ( dy ) {
            LONG dy1, dy2;

            /* fetch scan-line y. */
            dy1 = y - y1;       /* d4 - delta y1. */
            dy2 = y - y2;       /* d3 - delta y2. */

            /*
             * Determine whether the current vector intersects with the scan
             * line we wish to draw.  This test is performed by computing the
             * y-deltas of the two endpoints from the scan line.
             * If both deltas have the same sign, then the line does
             * not intersect and can be ignored.  The origin for this
             * test is found in Newman and Sproull.
             */
            if ((dy1 < 0) != (dy2 < 0)) {
                int dx = (x2 - x1) << 1;    /* so we can round by adding 1 below */
                if (++intersections > MAX_INTERSECTIONS)
                    break;
                /* fill edge buffer with x-values */
                if ( dx < 0 ) {
                    *bufptr++ = ((dy2 * dx / dy + 1) >> 1) + x2;
                }
                else {
                    *bufptr++ = ((dy1 * dx / dy + 1) >> 1) + x1;
                }
            }
        }
    }

    /*
     * All of the points of intersection have now been found.  If there
     * were none then there is nothing more to do.  Otherwise, sort the
     * list of points of intersection in ascending order.
     * (The list contains only the x-coordinates of the points.)
     */

    /* anything to do? */
    if (intersections == 0)
        return;

    /* bubblesort the intersections, if it makes sense */
    if ( intersections > 1 )
        bub_sort(fill_buffer, intersections);

    if (attr->clip) {
        /* Clipping is in force.  Once the endpoints of the line segment have */
        /* been adjusted for the border, clip them to the left and right sides */
        /* of the clipping rectangle. */

        /* The x-coordinates of each line segment are adjusted so that the */
        /* border of the figure will not be drawn with the fill pattern. */

        /* loop through buffered points */
        WORD * ptr = fill_buffer;
        for (i = intersections / 2 - 1; i >= 0; i--) {
            WORD x1, x2;
            Rect rect;

            /* grab a pair of adjusted intersections */
            x1 = *ptr++ + 1;
            x2 = *ptr++ - 1;

            /* do nothing, if starting point greater than ending point */
            if ( x1 > x2 )
                continue;

            if ( x1 < clipper->xmn_clip ) {
                if ( x2 < clipper->xmn_clip )
                    continue;           /* entire segment clipped left */
                x1 = clipper->xmn_clip; /* clip left end of line */
            }

            if ( x2 > clipper->xmx_clip ) {
                if ( x1 > clipper->xmx_clip )
                    continue;           /* entire segment clippped */
                x2 = clipper->xmx_clip; /* clip right end of line */
            }
            rect.x1 = x1;
            rect.y1 = y;
            rect.x2 = x2;
            rect.y2 = y;

            /* rectangle fill routine draws horizontal line */
            draw_rect_common(attr, &rect);
        }
    }
    else {
        /* Clipping is not in force.  Draw from point to point. */

        /* This code has been modified from the version in the screen driver. */
        /* The x-coordinates of each line segment are adjusted so that the */
        /* border of the figure will not be drawn with the fill pattern.  If */
        /* the starting point is greater than the ending point then nothing is */
        /* done. */

        /* loop through buffered points */
        WORD * ptr = fill_buffer;
        for (i = intersections / 2 - 1; i >= 0; i--) {
            WORD x1, x2;
            Rect rect;

            /* grab a pair of adjusted endpoints */
            x1 = *ptr++ + 1 ;   /* word */
            x2 = *ptr++ - 1 ;   /* word */

            /* If starting point greater than ending point, nothing is done. */            /* is start still to left of end? */
            if ( x1 <= x2 ) {
                rect.x1 = x1;
                rect.y1 = y;
                rect.x2 = x2;
                rect.y2 = y;

                /* rectangle fill routine draws horizontal line */
                draw_rect_common(attr, &rect);
            }
        }
    }
}


/*
 * polygon - draw a filled polygon
 */

void
polygon(Vwk * vwk, Point * ptsin, int count)
{
    WORD i, k, y;
    WORD fill_maxy, fill_miny;
    Point * point, * ptsget, * ptsput;
    VwkClip *clipper;
    VwkAttrib attr;

    LSTLIN = FALSE;

    /* find out the total min and max y values */
    point = ptsin;
    fill_maxy = fill_miny = point->y;
    for (i = count - 1; i > 0; i--) {
        point++;
        k = point->y;

        if (k < fill_miny)
            fill_miny = k;
        else
            if (k > fill_maxy)
                fill_maxy = k;
    }

    if (vwk->clip) {
        if (fill_miny < vwk->ymn_clip) {
            if (fill_maxy >= vwk->ymn_clip) {
                /* polygon starts before clip */
                fill_miny = vwk->ymn_clip - 1;       /* polygon partial overlap */
                if (fill_miny < 1)
                    fill_miny = 1;
            } else
                return;         /* polygon entirely before clip */
        }
        if (fill_maxy > vwk->ymx_clip) {
            if (fill_miny <= vwk->ymx_clip)  /* polygon ends after clip */
                fill_maxy = vwk->ymx_clip;   /* polygon partial overlap */
            else
                return;         /* polygon entirely after clip */
        }
    }

    /* close the polygon, connect last and first point */
    ptsget = ptsin;
    ptsput = ptsin + count;
    ptsput->x = ptsget->x;
    ptsput->y = ptsget->y;

    /* cast structure needed by clc_flit */
    clipper = VDI_CLIP(vwk);
    /* copy data needed by clc_flit -> draw_rect_common */
    Vwk2Attrib(vwk, &attr, vwk->fill_color);

    /* really draw it */
    for (y = fill_maxy; y > fill_miny; y--) {
        clc_flit(&attr, clipper, ptsin, y, count);
    }
    if (vwk->fill_per == TRUE) {
        LN_MASK = 0xffff;
        polyline(vwk, ptsin, count+1, vwk->fill_color);
    }
}



/*
 * _v_fillarea - Fill an area
 */

void
_v_fillarea(Vwk * vwk)
{
    Point * point = (Point*)PTSIN;
    int count = CONTRL[1];

#if 0
#if HAVE_BEZIER
    /* check, if we want to draw a filled bezier curve */
    if (CONTRL[5] == 13 && vwk->bez_qual )
        v_bez_fill(vwk, point, count);
    else
#endif
#endif
        polygon(vwk, point, count);
}



/*
 * clipbox - Just clips and copies the inputs for use by "rectfill"
 *
 * input:
 *     X1        = x coord of upper left corner.
 *     Y1        = y coord of upper left corner.
 *     X2        = x coord of lower right corner.
 *     Y2        = y coord of lower right corner.
 *     vwk->clip = clipping flag. (0 => no clipping.)
 *     vwk->xmn_clip = x clipping minimum.
 *     vwk->xmx_clip = x clipping maximum.
 *     vwk->ymn_clip = y clipping minimum.
 *     vwk->ymx_clip = y clipping maximum.
 *
 * output:
 *     X1 = x coord of upper left corner.
 *     Y1 = y coord of upper left corner.
 *     X2 = x coord of lower right corner.
 *     Y2 = y coord of lower right corner.
 */

static BOOL
clipbox(Vwk * vwk, Rect * rect)
{
    WORD x1, y1, x2, y2;

    x1 = rect->x1;
    y1 = rect->y1;
    x2 = rect->x2;
    y2 = rect->y2;

    /* clip x coordinates */
    if ( x1 < vwk->xmn_clip) {
        if (x2 < vwk->xmn_clip) {
            return(FALSE);             /* clipped box is null */
        }
        rect->x1 = vwk->xmn_clip;
    }
    if ( x2 > vwk->xmx_clip) {
        if (x1 > vwk->xmx_clip) {
            return(FALSE);             /* clipped box is null */
        }
        rect->x2 = vwk->xmx_clip;
    }
    /* clip y coordinates */
    if ( y1 < vwk->ymn_clip) {
        if (y2 < vwk->ymn_clip) {
            return(FALSE);             /* clipped box is null */
        }
        rect->y1 = vwk->ymn_clip;
    }
    if ( y2 > vwk->ymx_clip) {
        if (y1 > vwk->ymx_clip) {
            return(FALSE);             /* clipped box is null */
        }
        rect->y2 = vwk->ymx_clip;
    }
    return (TRUE);
}


/*
 * get_color - Get color value of requested pixel.
 */
static UWORD
get_color (UWORD mask, UWORD * addr)
{
    UWORD color = 0;                    /* clear the pixel value accumulator. */
    WORD plane = v_planes;

    while(1) {
        /* test the bit. */
        if ( *--addr & mask )
            color |= 1;         /* if 1, set color accumulator bit. */

        if ( --plane == 0 )
            break;

        color <<= 1;            /* shift accumulator for next bit_plane. */
    }

    return color;       /* this is the color we are searching for */
}

/*
 * pixelread - gets a pixel's color index value
 *
 * input:
 *     PTSIN(0) = x coordinate.
 *     PTSIN(1) = y coordinate.
 * output:
 *     pixel value
 */

static UWORD
pixelread(const WORD x, const WORD y)
{
    UWORD *addr;
    UWORD mask;

    /* convert x,y to start adress and bit mask */
    addr = get_start_addr(x, y);
    addr += v_planes;                   /* start at highest-order bit_plane */
    mask = 0x8000 >> (x&0xf);           /* initial bit position in WORD */

    return get_color(mask, addr);       /* return the composed color value */
}

static UWORD
search_to_right (Vwk * vwk, WORD x, UWORD mask, const UWORD search_col, UWORD * addr)
{
    /* is x coord < x resolution ? */
    while( x++ < vwk->xmx_clip ) {
        UWORD color;

        /* need to jump over interleaved bit_plane? */
        mask = mask >> 1 | mask << 15;  /* roll right */
        if ( mask & 0x8000 )
            addr += v_planes;

        /* search, while pixel color != search color */
        color = get_color(mask, addr);
        if ( search_col != color ) {
            break;
        }

    }

    return x - 1;       /* output x coord -1 to endxright. */
}

static UWORD
search_to_left (Vwk * vwk, WORD x, UWORD mask, const UWORD search_col, UWORD * addr)
{
    /* Now, search to the left. */
    while (x-- > vwk->xmn_clip) {
        UWORD color;

        /* need to jump over interleaved bit_plane? */
        mask = mask >> 15 | mask << 1;  /* roll left */
        if ( mask & 0x0001 )
            addr -= v_planes;

        /* search, while pixel color != search color */
        color = get_color(mask, addr);
        if ( search_col != color )
            break;

    }

    return x + 1;       /* output x coord + 1 to endxleft. */
}

/*
 * end_pts - find the endpoints of a section of solid color
 *
 *   (for the _seed_fill routine.)
 *
 * input:  4(sp) = xstart.
 *         6(sp) = ystart.
 *         8(sp) = ptr to endxleft.
 *         C(sp) = ptr to endxright.
 *
 * output: endxleft  := left endpoint of solid color.
 *         endxright := right endpoint of solid color.
 *         d0        := success flag.
 *             0 => no endpoints or xstart on edge.
 *             1 => endpoints found.
 *         seed_type  indicates the type of fill
 */

static WORD
end_pts(Vwk * vwk, WORD x, WORD y, WORD *xleftout, WORD *xrightout,
        BOOL seed_type)
{
    UWORD color;
    UWORD * addr;
    UWORD mask;

    /* see, if we are in the y clipping range */
    if ( y < vwk->ymn_clip || y > vwk->ymx_clip)
        return 0;

    /* convert x,y to start adress and bit mask */
    addr = get_start_addr(x, y);
    addr += v_planes;                   /* start at highest-order bit_plane */
    mask = 0x8000 >> (x & 0x000f);   /* fetch the pixel mask. */

    /* get search color and the left and right end */
    color = get_color (mask, addr);
    *xrightout = search_to_right (vwk, x, mask, color, addr);
    *xleftout = search_to_left (vwk, x, mask, color, addr);

    /* see, if the whole found segment is of search color? */
    if ( color != search_color ) {
        return seed_type ^ 1;   /* return segment not of search color */
    }
    return seed_type ^ 0;       /* return segment is of search color */
}

/* Prototypes local to this module */
static WORD
get_seed(Vwk * vwk, WORD xin, WORD yin, WORD *xleftout, WORD *xrightout,
             BOOL seed_type);


void
d_contourfill(Vwk * vwk)
{
    WORD newxleft;              /* ends of line at oldy +       */
    WORD newxright;             /* the current direction    */
    WORD oldxleft;              /* left end of line at oldy     */
    WORD oldxright;             /* right end                    */
    WORD oldy;                  /* the previous scan line       */
    WORD xleft;                 /* temporary endpoints          */
    WORD xright;                /* */
    WORD direction;             /* is next scan line up or down */
    BOOL notdone;               /* does seedpoint==search_color */
    BOOL gotseed;               /* a seed was put in the Q      */
    BOOL seed_type;             /* indicates the type of fill */

    xleft = PTSIN[0];
    oldy = PTSIN[1];

    if (xleft < vwk->xmn_clip || xleft > vwk->xmx_clip ||
        oldy < vwk->ymn_clip  || oldy > vwk->ymx_clip)
        return;

    search_color = INTIN[0];

    if ((WORD)search_color < 0) {
        search_color = pixelread(xleft,oldy);
        seed_type = 1;
    } else {
        const WORD plane_mask[] = { 1, 3, 7, 15 };

        /* Range check the color and convert the index to a pixel value */
        if (search_color >= DEV_TAB[13])
            return;

        /*
         * We mandate that white is all bits on.  Since this yields 15
         * in rom, we must limit it to how many planes there really are.
         * Anding with the mask is only necessary when the driver supports
         * move than one resolution.
         */
        search_color =
            (MAP_COL[search_color] & plane_mask[INQ_TAB[4] - 1]);
        seed_type = 0;
    }

    /* Initialize the line drawing parameters */
    LSTLIN = FALSE;

    notdone = end_pts(vwk, xleft, oldy, &oldxleft, &oldxright, seed_type);

    qptr = qbottom = 0;
    qtop = 3;                   /* one above highest seed point */
    queue[0] = (oldy | DOWN_FLAG);
    queue[1] = oldxleft;
    queue[2] = oldxright;           /* stuff a point going down into the Q */

    if (notdone) {
        /* couldn't get point out of Q or draw it */
        while (1) {
            Rect rect;

            direction = (oldy & DOWN_FLAG) ? 1 : -1;
            gotseed = get_seed(vwk, oldxleft, (oldy + direction),
                               &newxleft, &newxright, seed_type);

            if ((newxleft < (oldxleft - 1)) && gotseed) {
                xleft = oldxleft;
                while (xleft > newxleft) {
                    --xleft;
                    get_seed(vwk, xleft, oldy ^ DOWN_FLAG,
                             &xleft, &xright, seed_type);
                }
            }
            while (newxright < oldxright) {
                ++newxright;
                gotseed = get_seed(vwk, newxright, oldy + direction,
                                   &xleft, &newxright, seed_type);
            }
            if ((newxright > (oldxright + 1)) && gotseed) {
                xright = oldxright;
                while (xright < newxright) {
                    ++xright;
                    get_seed(vwk, xright, oldy ^ DOWN_FLAG,
                             &xleft, &xright, seed_type);
                }
            }

            /* Eventually jump out here */
            if (qtop == qbottom)
                break;

            while (queue[qptr] == EMPTY) {
                qptr += 3;
                if (qptr == qtop)
                    qptr = qbottom;
            }

            oldy = queue[qptr];
            queue[qptr++] = EMPTY;
            oldxleft = queue[qptr++];
            oldxright = queue[qptr++];
            if (qptr == qtop)
                crunch_queue();

            rect.x1 = oldxleft;
            rect.y1 = ABS(oldy);
            rect.x2 = oldxright;
            rect.y2 = ABS(oldy);

            /* rectangle fill routine draws horizontal line */
            draw_rect(vwk, &rect, vwk->fill_color);
        }
    }
}                               /* end of fill() */

/*
 * crunch_queue - move qtop down to remove unused seeds
 */
static void
crunch_queue(void)
{
    while ((queue[qtop - 3] == EMPTY) && (qtop > qbottom))
        qtop -= 3;
    if (qptr >= qtop)
        qptr = qbottom;
}

/*
 * get_seed - put seeds into Q, if (xin,yin) is not of search_color
 */
static WORD
get_seed(Vwk * vwk, WORD xin, WORD yin, WORD *xleftout, WORD *xrightout,
         BOOL seed_type)
{
    if (end_pts(vwk, xin, ABS(yin), xleftout, xrightout, seed_type)) {
        /* false if of search_color */
        for (qtmp = qbottom, qhole = EMPTY; qtmp < qtop; qtmp += 3) {
            /* see, if we ran into another seed */
            if ( ((queue[qtmp] ^ DOWN_FLAG) == yin) && (queue[qtmp] != EMPTY) &&
                (queue[qtmp + 1] == *xleftout) )

            {
                /* we ran into another seed so remove it and fill the line */
                Rect rect;

                rect.x1 = *xleftout;
                rect.y1 = ABS(yin);
                rect.x2 = *xrightout;
                rect.y2 = ABS(yin);

                /* rectangle fill routine draws horizontal line */
                draw_rect(vwk, &rect, vwk->fill_color);

                queue[qtmp] = EMPTY;
                if ((qtmp + 3) == qtop)
                    crunch_queue();
                return 0;
            }
            if ((queue[qtmp] == EMPTY) && (qhole == EMPTY))
                qhole = qtmp;
        }

        if (qhole == EMPTY) {
            if ((qtop += 3) > QMAX) {
                qtmp = qbottom;
                qtop -= 3;
            }
        } else
            qtmp = qhole;

        queue[qtmp++] = yin;    /* put the y and endpoints in the Q */
        queue[qtmp++] = *xleftout;
        queue[qtmp] = *xrightout;
        return 1;             /* we put a seed in the Q */
    }

    return 0;           /* we didnt put a seed in the Q */
}



void
_v_get_pixel(Vwk * vwk)
{
    WORD pel;
    WORD *int_out;
    const WORD x = PTSIN[0];       /* fetch x coord. */
    const WORD y = PTSIN[1];       /* fetch y coord. */

    /* Get the requested pixel */
    pel = (WORD)pixelread(x,y);

    int_out = INTOUT;
    *int_out++ = pel;

    *int_out = REV_MAP_COL[pel];
    CONTRL[4] = 2;
}



/*
 * get_pix - gets a pixel (just for linea!)
 *
 * input:
 *     PTSIN(0) = x coordinate.
 *     PTSIN(1) = y coordinate.
 * output:
 *     pixel value
 */
WORD
get_pix(void)
{
    /* return the composed color value */
    return pixelread(PTSIN[0], PTSIN[1]);
}

/*
 * put_pix - plot a pixel (just for linea!)
 *
 * input:
 *     INTIN(0) = pixel value.
 *     PTSIN(0) = x coordinate.
 *     PTSIN(1) = y coordinate.
 */
void
put_pix(void)
{
    UWORD *addr;
    UWORD color;
    UWORD mask;
    int plane;

    const WORD x = PTSIN[0];
    const WORD y = PTSIN[1];

    /* convert x,y to start adress */
    addr = get_start_addr(x, y);
    /* co-ordinates can wrap, but cannot write outside screen,
     * alternatively this could check against v_bas_ad+vram_size()
     */
    if (addr < (UWORD*)v_bas_ad || addr >= get_start_addr(v_hz_rez, v_vt_rez)) {
	    return;
    }
    color = INTIN[0];           /* device dependent encoded color bits */
    mask = 0x8000 >> (x&0xf);   /* initial bit position in WORD */

    for (plane = v_planes-1; plane >= 0; plane-- ) {
        color = color >> 1| color << 15;        /* rotate color bits */
        if (color&0x8000)
            *addr++ |= mask;
        else
            *addr++ &= ~mask;
    }
}
