/**
 ** drwcpoly.c ---- draw the outline of a custom (wide and/or dashed) 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.
 **
 ** 190608, M.Alvarez, solved a very old problem, the intersect function gave
 **                    bad results for solid and pattern filed wide lines,
 **                    changed to a simple one that works ok except for circles
 **                    and dashed lines, so we use part of the original code
 **                    in these cases.
 ** 220714, M.Alvarez, found problems in the intersect function playing with
 **                    many edges polygons, check bad cases and use the old
 **                    code instead.
 **/

#include "libgrx.h"
#include "shapes.h"
#include "clipping.h"
#include "arith.h"

/*
 * update the end point of line #1 and the starting point of line #2
 * so that they intersect
 */

#define x11 l1s[0]
#define y11 l1s[1]
#define x12 l1e[0]
#define y12 l1e[1]
#define x21 l2s[0]
#define y21 l2s[1]
#define x22 l2e[0]
#define y22 l2e[1]

#define CHECK_DIR(a, b, new) \
    (((int)((unsigned int)(a - b) ^ (unsigned int)(a - new))) >= 0)

static void intersect2(int l1s[2],int l1e[2],int l2s[2],int l2e[2]);

static void intersect1(int l1s[2],int l1e[2],int l2s[2],int l2e[2])
{
    // this code for solid and pattern filed wide lines
    
    if(x12 == x21 && y12 == y21) return; // nothing to do

    int dx1 = x11 - x12;
    int dy1 = y11 - y12;
    int dx2 = x21 - x22;
    int dy2 = y21 - y22;
    long det = imul32(dx1,dy2) - imul32(dy1,dx2);
    if (det != 0) {
        long newx = ((imul32(x11,y12) - imul32(y11,x12)) * dx2 -
                    (imul32(x21,y22) - imul32(y21,x22)) * dx1) / det;
        long newy = ((imul32(x11,y12) - imul32(y11,x12)) * dy2 -
                    (imul32(x21,y22) - imul32(y21,x22)) * dy1) / det;
        // check if the point is in the correct direction
        if (CHECK_DIR(x11, x12, newx) && CHECK_DIR(x22, x21, newx) &&
            CHECK_DIR(y11, y12, newy) && CHECK_DIR(y22, y21, newy) ) {
            l1e[0] = l2s[0] = (int)newx;
            l1e[1] = l2s[1] = (int)newy;
            return;
        }
    }
    intersect2(l1s, l1e, l2s, l2e);
}

static void intersect2(int l1s[2],int l1e[2],int l2s[2],int l2e[2])
{
    // this code for circles and dashed lines
    
    if(x12 == x21 && y12 == y21) return; // nothing to do

    int  dx1 = x12 - x11;
    int  dy1 = y12 - y11;
    int  dx2 = x22 - x21;
    int  dy2 = y22 - y21;

    /* Check mean point and its eight neighbours for
    ** best compromise intersection point
    **
    **  y-y11   y12-y11           y-y21   y22-y21
    **  ----- - ------- = 0  and  ----- - ------- = 0
    **  x-x11   x12-x11           x-x21   x22-x21
    **
    ** should hold for intersection point (x,y)
    ** Measuring the errors for both lines:
    **
    ** e1 = (x12-x11)(y-y11)-(x-x11)(y12-y11) = dx1(y-y11)-(x-x11)dy1
    ** e2 = (x22-x21)(y-y21)-(x-x21)(y22-y21) = dx2(y-y21)-(x-x21)dy2
    **
    ** search minimal err = |e1| + |e2|
    */

    static int xr[9] = { 0, -1, 0, 1,  0, -1, 1,  1, -1};
    static int yr[9] = { 0,  0, 1, 0, -1,  1, 1, -1, -1};
    int xc = (x12+x21) >> 1;
    int yc = (y12+y21) >> 1;
    int xb = 0, yb = 0;
    int i;
    long minerr=0, err;
    for (i = 0; i < 9; ++i) {
        int x = xc+xr[i];
        int y = yc+yr[i];
        long e1 = imul32(dx1,(y-y11)) - imul32(dy1,(x-x11));
        long e2 = imul32(dx2,(y-y21)) - imul32(dy2,(x-x21));
        err = labs(e1) + labs(e2);
        if ( i==0 || err < minerr) {
            minerr = err;
            xb = xr[i]; yb = yr[i];
            if (minerr == 0) break;
        }
    }
    l1e[0] = l2s[0] = xc + xb;
    l1e[1] = l2s[1] = yc + yb;
}

#undef x11
#undef y11
#undef x12
#undef y12
#undef x21
#undef y21
#undef x22
#undef y22

/*
 * generate the four corner points of a wide line segment
 *
 * p1 end : rect[0]  rect[1]
 * p2 end : rect[2]  rect[3]
 *
 */
static void genrect(int p1[2],int p2[2],int w,int rect[4][2])
{
    int dx  = p2[0] - p1[0];
    int dy  = p2[1] - p1[1];
    int wx,wy,wx1,wy1,wx2,wy2;

    if(dx == 0) {
        wx = w;
        wy = 0;
    }
    else if (dy == 0) {
       wx = 0;
       wy = w;
    }
    else {
        unsigned long minerr,error = ~0UL,w2 = imul32(w,w);
        int mindelta = umin(iabs(dx),iabs(dy));
        int maxdelta = umax(iabs(dx),iabs(dy));
        wx1 = w/2;
        if (wx1 <= 0) wx1 = 1;
        if (wx1+wx1 < w) ++wx1;
        wy1 = 0;
        do {
            wx  = wx1++;
            wy  = wy1;
            wy1 = urscale(wx1,mindelta,maxdelta);
            minerr = error;
            error  = imul32(wx1,wx1) + imul32(wy1,wy1) - w2;
            error  = labs(error);
        } while(error <= minerr);
        if(iabs(dx) > iabs(dy)) iswap(wx,wy);
    }
    if(dx <  0) wy = (-wy);
    if(dy >= 0) wx = (-wx);
    wx1 = -(wx >> 1);
    wy1 = -(wy >> 1);
    wx2 = wx + wx1;
    wy2 = wy + wy1;
    if((wx1 + wx2) < 0) wx1++,wx2++;
    if((wy1 + wy2) < 0) wy1++,wy2++;
    rect[0][0] = p1[0] + wx1;
    rect[0][1] = p1[1] + wy1;
    rect[1][0] = p1[0] + wx2;
    rect[1][1] = p1[1] + wy2;
    rect[2][0] = p2[0] + wx2;
    rect[2][1] = p2[1] + wy2;
    rect[3][0] = p2[0] + wx1;
    rect[3][1] = p2[1] + wy1;
}

/*
 * working version of the line pattern and fill argument structures
 */
typedef struct {
    int       w;                /* line width */
    int       psegs;            /* number of pattern segments */
    int       plength;          /* total length of pattern in pixels */
    int       ppos;             /* current pattern position (modulo plength) */
    int       on;               /* is the pattern currently on ? */
    unsigned char *patt;        /* the pattern bits */
    GrFiller *f;                /* the filler functions */
    GrFillArg c;                /* the filler argument */
    int       intersect;        /* what intersect routine to use if != 2 then 1*/
} linepatt;

static void solidsegment1(
    int p1[2],  int p2[2],
    int prev[2],int next[2],
    linepatt *p)
{
    int x1 = p1[0], y1 = p1[1];
    int x2 = p2[0], y2 = p2[1];
    (*p->f->line)(
        (x1 + CURC->gc_xoffset),
        (y1 + CURC->gc_yoffset),
        (x2 - x1),
        (y2 - y1),
        p->c
    );
}

static void solidsegmentw(
    int p1[2],  int p2[2],
    int prev[2],int next[2],
    linepatt *p)
{
    void (*intersect)(int[2],int[2],int[2],int[2]);
    int rect[4][2], prect[4][2], nrect[4][2];
    
    intersect = (p->intersect == 2) ? intersect2 : intersect1;
    genrect(p1,p2,p->w,rect);
    if(prev) genrect(prev,p1,p->w,prect);
    if(next) genrect(p2,next,p->w,nrect);

    if(prev && next) {
        int points[2];
        points[0] = rect[1][0]; points[1] = rect[1][1];
        (*intersect)(prect[1],prect[2],rect[1],rect[2]);
        (*intersect)(points,rect[2],nrect[1],nrect[2]);
        points[0] = rect[0][0]; points[1] = rect[0][1];
        (*intersect)(prect[0],prect[3],rect[0],rect[3]);
        (*intersect)(points,rect[3],nrect[0],nrect[3]);
    } else
    if(prev) {
        (*intersect)(prect[1],prect[2],rect[1],rect[2]);
        (*intersect)(prect[0],prect[3],rect[0],rect[3]);
    } else
    if(next) {
        (*intersect)(rect[1],rect[2],nrect[1],nrect[2]);
        (*intersect)(rect[0],rect[3],nrect[0],nrect[3]);
    }

    _GrScanConvexPoly(4,rect,p->f,p->c);
}

static void dashedsegment(
    int p1[2],  int p2[2],
    int prev[2],int next[2],
    linepatt *p,
    void (*doseg)(int[2],int[2],int[2],int[2],linepatt*))
{
    int on,pos,len,seg;
    int x,y,dx,dy;
    int error,erradd,errsub,count;
    int xinc1,xinc2,yinc1,yinc2;
    int start[2],end[2], se_init;

    p->intersect = 2;
    /* find the current starting segment for the pattern */
    pos = (p->ppos %= p->plength);
    for(on = 1,seg = 0; ; ) {
        len = p->patt[seg];
        if(pos < len) { len -= pos; break; }
        if(++seg >= p->psegs) seg = 0;
        on  ^= 1;
        pos -= len;
    }
    /* Can't have a zero length element here */

    /* set up line drawing */
    x = p1[0]; dx = p2[0] - x;
    y = p1[1]; dy = p2[1] - y;
    if(dx >= 0) { xinc2 =  1; }
    else        { xinc2 = -1; dx = -dx; }
    if(dy >= 0) { yinc2 =  1; }
    else        { yinc2 = -1; dy = -dy; }
    if(dx >= dy) {
        count  = dx +  1;
        error  = dx >> 1;
        errsub = dy;
        erradd = dx;
        xinc1  = xinc2;
        yinc1  = 0;
    }
    else {
        count  = dy +  1;
        error  = dy >> 1;
        errsub = dx;
        erradd = dy;
        yinc1  = yinc2;
        xinc1  = 0;
    }
    se_init = 0;
    if(on) {
        start[0] = x;
        start[1] = y;
        se_init = 1;
    }
    else {
        prev = NULL;
    }
    /* go */
    while(--count >= 0) {
        if(on) {
            end[0] = x;
            end[1] = y;
            se_init |= 2;
        }
        if((error -= errsub) < 0) {
            error += erradd;
            x += xinc2;
            y += yinc2;
        }
        else {
            x += xinc1;
            y += yinc1;
        }
        if(--len <= 0) {
            /* end of current segment */
            int old_state = on;
            do {
              if(++seg >= p->psegs) seg = 0;
              len = p->patt[seg];
              on ^= 1;
            } while (len == 0);
            if ( !old_state &&  on && count > 0) {
                start[0] = x;
                start[1] = y;
                se_init = 1;
            } else
            if (  old_state && !on) {
                (*doseg)(start,end,prev,NULL,p);
                prev = NULL;
                se_init = 0;
            }
            /* else: zero length element(s), continue current segment */
        }
    }
    if(on && se_init==3) {
        (*doseg)(start,end,prev,next,p);
    }
    p->on = on;
}

static void dashedsegment1(
    int p1[2],  int p2[2],
    int prev[2],int next[2],
    linepatt *p)
{
    dashedsegment(p1,p2,prev,next,p,solidsegment1);
}

static void dashedsegmentw(
    int p1[2],  int p2[2],
    int prev[2],int next[2],
    linepatt *p)
{
    dashedsegment(p1,p2,prev,next,p,solidsegmentw);
}

void _GrDrawCustomPolygon(
     int n,int pt[][2],
     const GrLineOption *lp,
     GrFiller *f,GrFillArg c,
     int doClose,int circle)
{
#define x1 start[0]
#define y1 start[1]
#define x2 end[0]
#define y2 end[1]

    int  i,start[2],end[2];
    void (*doseg)(int[2],int[2],int[2],int[2],linepatt*);
    linepatt  p;
    GrContext preclip;
    if(n < 2) return;
    /* set up working pattern */
    p.f       = f;
    p.c       = c;
    p.w       = imax((lp->lno_width - 1),0);
    p.ppos    = 0;
    p.patt    = lp->lno_dashpat;
    p.psegs   = p.patt ? imax(lp->lno_pattlen,0) : 0;
    p.plength = 0;
    p.intersect = circle ? 2 : 1;
    for(i = 0; i < p.psegs; i++) {
/*          if(!p.patt[i]) { p.plength = 0; break; } */
        p.plength += p.patt[i];
    }
    if(p.plength)
        doseg = p.w ? dashedsegmentw : dashedsegment1;
    else {
        if (p.psegs && p.patt[0]==0 ) return; /* nothing to do */
        doseg = p.w ? solidsegmentw : solidsegment1;
    }
    /* preclip */
    x1 = x2 = pt[0][0];
    y1 = y2 = pt[0][1];
    for(i = 1; i < n; i++) {
        int *ppt = pt[i];
        if(x1 > ppt[0]) x1 = ppt[0];
        if(x2 < ppt[0]) x2 = ppt[0];
        if(y1 > ppt[1]) y1 = ppt[1];
        if(y2 < ppt[1]) y2 = ppt[1];
    }
    sttcopy(&preclip,CURC);
    preclip.gc_xcliplo -= p.w; preclip.gc_ycliplo -= p.w;
    preclip.gc_xcliphi += p.w; preclip.gc_ycliphi += p.w;
    clip_ordbox((&preclip),x1,y1,x2,y2);
    mouse_block(CURC,x1,y1,x2,y2);
    /* do the polygon segments */
    if(doClose) {
        int *p1 = pt[0], *p2 = pt[n - 1];
        if((n > 1) && (p1[0] == p2[0]) && (p1[1] == p2[1])) n--;
        if(n < 3) doClose = FALSE;
    }
    for(i = 0; i < n; i++) {
        int clipped,xmajor,length;
        int *p1,*p2,*prev,*next;
        if(!(i + doClose)) continue;
        p1 = pt[(i + n - 1) % n];
        p2 = pt[i];
        prev = ((i > 1) || doClose) ? pt[(i + n - 2) % n] : NULL;
        next = ((i < (n - 1)) || doClose) ? pt[(i + 1) % n] : NULL;
        x1 = p1[0];
        y1 = p1[1];
        x2 = p2[0];
        y2 = p2[1];
        clipped = 0;
        xmajor  = iabs(x1 - x2);
        length  = iabs(y1 - y2);
        if(xmajor > length) { length = xmajor; xmajor = 1; }
        else xmajor = 0;
        clip_line_((&preclip),x1,y1,x2,y2,goto outside,clipped = p.plength);
        if(clipped) {
            clipped = xmajor ? iabs(p1[0] - x1) : iabs(p1[1] - y1);
            p.ppos += clipped;
            length -= clipped;
        }
        (*doseg)(start,end,prev,next,&p);
      outside:
        p.ppos += length;
    }
    mouse_unblock();
#undef x1
#undef y1
#undef x2
#undef y2
}


