/**
 ** scanpoly.c ---- scan fill an arbitrary polygon
 **
 ** Copyright (c) 1995 Csaba Biegl, 820 Stirrup Dr, Nashville, TN 37221
 ** [e-mail: csaba@vuse.vanderbilt.edu]
 **
 ** This file is part of the GRX graphics library.
 **
 ** The GRX graphics library is free software; you can redistribute it
 ** and/or modify it under some conditions; see the "copying.grx" file
 ** for details.
 **
 ** This library is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 **
 **/

#include "libgrx.h"
#include "shapes.h"
#include "clipping.h"
#include "arith.h"
#include "shape/polyedge.h"

typedef enum {
    inactive,                           /* not reached yet */
    active,                             /* currently contributes point */
    passed                              /* above current scan line */
} edgestat;

typedef struct {
    edgestat status;                    /* status of this edge */
    polyedge e;                         /* the edge data */
} edge;

typedef struct _scan {
    struct _scan *next;                 /* next segment/point in the list */
    int    x1,x2;                       /* endpoints of this filled segment */
} scan;

#define add_scanpoint(List,Scp,X1,X2) {                         \
    scan *prev = NULL;                                          \
    scan *work = List;                                          \
    while(work != NULL) {                                       \
        if(work->x1 > X1) break;                                \
        prev = work;                                            \
        work = work->next;                                      \
    }                                                           \
    Scp->x1   = X1;                                             \
    Scp->x2   = X2;                                             \
    Scp->next = work;                                           \
    if(prev) prev->next = Scp;                                  \
    else     List       = Scp;                                  \
}

#define add_scansegment(List,Scp,X1,X2) {                       \
    scan *prev   = NULL;                                        \
    scan *work   = List;                                        \
    int  overlap = FALSE;                                       \
    while(work != NULL) {                                       \
        if((work->x1 <= X2) && (X1 <= work->x2)) {              \
            overlap = TRUE;                                     \
            if(X1 < work->x1) work->x1 = X1;                    \
            if(X2 > work->x2) {                                 \
                prev = work;                                    \
                while((work = work->next) != NULL) {            \
                    if(work->x1 > X2) break;                    \
                    if(work->x2 > X2) X2 = work->x2;            \
                }                                               \
                prev->x2   = X2;                                \
                prev->next = work;                              \
            }                                                   \
            break;                                              \
        }                                                       \
        if(work->x1 > X2) break;                                \
        prev = work;                                            \
        work = work->next;                                      \
    }                                                           \
    if(!overlap) {                                              \
        Scp->x1   = X1;                                         \
        Scp->x2   = X2;                                         \
        Scp->next = work;                                       \
        if(prev) prev->next = Scp;                              \
        else     List       = Scp;                              \
    }                                                           \
}

void _GrScanPolygon(int n,int pt[][2],GrFiller *f,GrFillArg c)
{
    //static int max_n  = 0;
    edge *edges,*ep;
    scan *scans,*sp,*points,*segments;
    int  xmin,xmax,ymin,ymax;
    int  ypos,nedges;

    /*if (n > max_n) {
        max_n = n;
        printf("_GrScanPolygon max n: %d\n", max_n);
    }*/

    if((n > 1) &&
        (pt[0][0] == pt[n-1][0]) &&
        (pt[0][1] == pt[n-1][1])) {
            n--;
        }
    if(n < 1) {
        return;
    }

    edges = (edge *)malloc(sizeof(edge) * (n + 2));
    scans = (scan *)malloc(sizeof(scan) * ((n * 2) + 2));
    if(edges && scans) {
        /*
         * Build the edge table. Store only those edges which are in the
         * valid Y region. Clip them in Y if necessary. Store them with
         * the endpoints ordered by Y in the edge table.
         */
        int prevx = xmin = xmax = pt[0][0];
        int prevy = ymin = ymax = pt[0][1];
        nedges = 0;
        ep     = edges;
        while(--n >= 0) {
            if(pt[n][1] >= prevy) {
                ep->e.x     = prevx;
                ep->e.y     = prevy;
                ep->e.xlast = prevx = pt[n][0];
                ep->e.ylast = prevy = pt[n][1];
            }
            else {
                ep->e.xlast = prevx;
                ep->e.ylast = prevy;
                ep->e.x     = prevx = pt[n][0];
                ep->e.y     = prevy = pt[n][1];
            }
            if((ep->e.y > GrHighY()) || (ep->e.ylast < GrLowY())) continue;
            clip_line_ymin(CURC,ep->e.x,ep->e.y,ep->e.xlast,ep->e.ylast);
            if(ymin > ep->e.y)     ymin = ep->e.y;
            if(ymax < ep->e.ylast) ymax = ep->e.ylast;
            if(xmin > ep->e.x)     xmin = ep->e.x;
            if(xmax < ep->e.xlast) xmax = ep->e.xlast;
            setup_edge(&ep->e);
            ep->status = inactive;
            nedges++;
            ep++;
        }
        if((nedges > 0) && (xmin <= GrHighX()) && (xmax >= GrLowX())) {
            if(xmin < GrLowX())  xmin = GrLowX();
            if(ymin < GrLowY())  ymin = GrLowY();
            if(xmax > GrHighX()) xmax = GrHighX();
            if(ymax > GrHighY()) ymax = GrHighY();
            mouse_block(CURC,xmin,ymin,xmax,ymax);
            /*
             * Scan for every row between ymin and ymax.
             * Build a linked list of disjoint segments to fill. Rules:
             *   (1) a horizontal edge in the row contributes a segment
             *   (2) any other edge crossing the row contributes a point
             *   (3) every segment between even and odd points is filled
             */
            for(ypos = ymin; ypos <= ymax; ypos++) {
                sp       = scans;
                points   = NULL;
                segments = NULL;
                for(n = nedges,ep = edges; --n >= 0; ep++) {
                    switch(ep->status) {
                        case inactive:
                            if(ep->e.y != ypos) break;
                            if(ep->e.dy == 0) {
                                ep->status = passed;
                                xmin = ep->e.x;
                                xmax = ep->e.xlast;
                                isort(xmin,xmax);
                                add_scansegment(segments,sp,xmin,xmax);
                                sp++;
                                break;
                            }
                            ep->status = active;
                        case active:
                            xmin = xmax = ep->e.x;
                            if(ep->e.ylast == ypos) {
                                ep->status = passed;
                                xmax = ep->e.xlast;
                                isort(xmin,xmax);
                                add_scanpoint(points,sp,xmin,xmax);
                                sp++;
                            }
                            else if(ep->e.xmajor) {
                                xstep_edge(&ep->e);
                                xmax = ep->e.x - ep->e.xstep;
                                isort(xmin,xmax);
                            }
                            else {
                                ystep_edge(&ep->e);
                            }
                            add_scanpoint(points,sp,xmin,xmax);
                            sp++;
                            break;
                        default:
                            break;
                    }
                }
                while(points != NULL) {
                    scan *nextpt = points->next;
                    if(!nextpt) break;
                    xmin   = points->x1;
                    xmax   = nextpt->x2;
                    points = nextpt->next;
                    add_scansegment(segments,nextpt,xmin,xmax);
                }
                while(segments != NULL) {
                    xmin     = segments->x1;
                    xmax     = segments->x2;
                    segments = segments->next;
                    clip_ordxrange_(CURC,xmin,xmax,continue,CLIP_EMPTY_MACRO_ARG);
                    (*f->scan)((xmin + CURC->gc_xoffset),
                               (ypos + CURC->gc_yoffset),
                               (xmax - xmin + 1),c);
                }
            }
            mouse_unblock();
        }
    }
    if (edges) free(edges);
    if (scans) free(scans);
}

