#pragma prototyped

#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <math.h>
#include "pathplan.h"
#include "solvers.h"

#define EPSILON1 1E-6
#define EPSILON2 1E-6

#define ABS(a) ((a) >= 0 ? (a) : -(a))

typedef struct tna_t {
    double t;
    Ppoint_t a[2];
} tna_t;

#define prerror(msg) \
        fprintf (stderr, "libpath/%s:%d: %s\n", __FILE__, __LINE__, (msg))

#define DISTSQ(a, b) ( \
    (((a).x - (b).x) * ((a).x - (b).x)) + (((a).y - (b).y) * ((a).y - (b).y)) \
)

#define POINTSIZE sizeof (Ppoint_t)

#define LT(pa, pbp) ((pa.y > pbp->y) || ((pa.y == pbp->y) && (pa.x < pbp->x)))
#define GT(pa, pbp) ((pa.y < pbp->y) || ((pa.y == pbp->y) && (pa.x > pbp->x)))

typedef struct p2e_t {
    Ppoint_t *pp;
    Pedge_t *ep;
} p2e_t;

typedef struct elist_t {
    Pedge_t *ep;
    struct elist_t *next, *prev;
} elist_t;

static p2e_t *p2es;
static int p2en;

static elist_t *elist;

static Ppoint_t *ops;
static int opn, opl;

static int reallyroutespline (Pedge_t *, int,
        Ppoint_t *, int, Ppoint_t, Ppoint_t);
static int mkspline (Ppoint_t *, int, tna_t *, Ppoint_t, Ppoint_t,
        Ppoint_t *, Ppoint_t *, Ppoint_t *, Ppoint_t *);
static int splinefits (Pedge_t *, int, Ppoint_t, Pvector_t, Ppoint_t, Pvector_t, int);
static int splineisinside (Pedge_t *, int, Ppoint_t *);
static int splineintersectsline (Ppoint_t *, Ppoint_t *, double *);
static void points2coeff (double, double, double, double, double *);
static void addroot (double, double *, int *);

static Pvector_t normv (Pvector_t);

static void growops (int);

static Ppoint_t add (Ppoint_t, Ppoint_t);
static Ppoint_t sub (Ppoint_t, Ppoint_t);
static double dist (Ppoint_t, Ppoint_t);
static Ppoint_t scale (Ppoint_t, double);
static double dot (Ppoint_t, Ppoint_t);
static double B0 (double t);
static double B1 (double t);
static double B2 (double t);
static double B3 (double t);
static double B01 (double t);
static double B23 (double t);
static int cmpp2efunc (const void *, const void *);

static void listdelete (Pedge_t *);
static void listreplace (Pedge_t *, Pedge_t *);
static void listinsert (Pedge_t *, Ppoint_t);

int Proutespline (Pedge_t *edges, int edgen, Ppolyline_t input,
        Ppoint_t *evs, Ppolyline_t *output) {
    Ppoint_t p0, p1, p2, p3;
    Ppoint_t *pp;
    Pvector_t v1, v2, v12, v23;
    int ipi, opi;
    int ei, p2ei;
    Pedge_t *e0p, *e1p;
	Ppoint_t	*inps;
	int			inpn;

	/* unpack into previous format rather than modify legacy code */
	inps = input.ps;
	inpn = input.pn;

#if 0
    if (!(p2es = (p2e_t *) malloc (sizeof (p2e_t) * (p2en = edgen * 2)))) {
        prerror ("cannot malloc p2es");
        abort ();
    }
    for (ei = 0, p2ei = 0; ei < edgen; ei++) {
        if (edges[ei].a.x == edges[ei].b.x && edges[ei].a.y == edges[ei].b.y)
            continue;
        p2es[p2ei].pp = &edges[ei].a;
        p2es[p2ei++].ep = &edges[ei];
        p2es[p2ei].pp = &edges[ei].b;
        p2es[p2ei++].ep = &edges[ei];
    }
    p2en = p2ei;
    qsort (p2es, p2en, sizeof (p2e_t), cmpp2efunc);
    elist = NULL;
    for (p2ei = 0; p2ei < p2en; p2ei += 2) {
        pp = p2es[p2ei].pp;
#if DEBUG >= 1
fprintf (stderr, "point: %d %lf %lf\n", p2ei, pp->x, pp->y);
#endif
        e0p = p2es[p2ei].ep;
        e1p = p2es[p2ei + 1].ep;
        p0 = (&e0p->a == p2es[p2ei].pp) ? e0p->b : e0p->a;
        p1 = (&e0p->a == p2es[p2ei + 1].pp) ? e1p->b : e1p->a;
        if (LT (p0, pp) && LT (p1, pp)) {
            listdelete (e0p), listdelete (e1p);
        } else if (GT (p0, pp) && GT (p1, pp)) {
            listinsert (e0p, *pp), listinsert (e1p, *pp);
        } else {
            if (LT (p0, pp))
                listreplace (e0p, e1p);
            else
                listreplace (e1p, e0p);
        }
    }
#endif
    /* generate the splines */
    evs[0] = normv (evs[0]);
    evs[1] = normv (evs[1]);
    opl = 0;
    growops (4);
    ops[opl++] = inps[0];
    if (reallyroutespline (edges, edgen, inps, inpn, evs[0], evs[1]) == -1)
        return -1;
	output->pn = opl;
	output->ps = ops;

#if 0
    fprintf (stderr, "%%!PS-splinomatic\n");
    fprintf (stderr, "%% edges\n");
	fprintf(stderr,"0 0 1 setrgbcolor\n");
	for (ipi = 0; ipi < edgen; ipi++)
        fprintf (stderr, "newpath %f %f moveto %f %f lineto stroke\n",
			edges[ipi].a.x,edges[ipi].a.y,
			edges[ipi].b.x,edges[ipi].b.y);

	fprintf (stderr, "%% input path\n");
	fprintf(stderr,"1 0 0 setrgbcolor\n");
	fprintf(stderr,"newpath");
    for (ipi = 0; ipi < inpn; ipi++) {
        fprintf (stderr, " %f %f", inps[ipi].x, inps[ipi].y);
		if (ipi == 0) fprintf(stderr," moveto");
		else fprintf(stderr," lineto");
	}
	fprintf(stderr," stroke\n");
	fprintf (stderr, "%% output spline\n");
	fprintf(stderr,"1 .9 .8 setrgbcolor\n");
	fprintf(stderr,"newpath");
    for (opi = 0; opi < opl; opi++) {
        fprintf (stderr, " %f %f", ops[opi].x, ops[opi].y);
		if (opi == 0) fprintf(stderr," moveto");
		else if (opi % 3 == 0) fprintf(stderr," curveto");
	}
	fprintf(stderr," stroke\n");
#endif

    return 0;
}

static int reallyroutespline (Pedge_t *edges, int edgen,
        Ppoint_t *inps, int inpn, Ppoint_t ev0, Ppoint_t ev1) {
    Ppoint_t p1, p2, p3, cp1, cp2, p;
    Pvector_t v1, v2, v12, v23, splitv, splitv1, splitv2;
    int ipi, opi;
    double maxd, d, t;
    int maxi, i, spliti;

    static tna_t *tnas;
    static int tnan;

    if (tnan < inpn) {
        if (!tnas) {
            if (!(tnas = malloc (sizeof (tna_t) * inpn)))
                return -1;
        } else {
            if (!(tnas = realloc (tnas, sizeof (tna_t) * inpn)))
                return -1;
        }
        tnan = inpn;
    }
    tnas[0].t = 0;
    for (i = 1; i < inpn; i++)
        tnas[i].t = tnas[i - 1].t + dist (inps[i], inps[i - 1]);
    for (i = 1; i < inpn; i++)
        tnas[i].t /= tnas[inpn - 1].t;
    for (i = 0; i < inpn; i++) {
        tnas[i].a[0] = scale (ev0, B1 (tnas[i].t));
        tnas[i].a[1] = scale (ev1, B2 (tnas[i].t));
    }
    if (mkspline (inps, inpn, tnas, ev0, ev1, &p1, &v1, &p2, &v2) == -1)
        return -1;
    if (splinefits (edges, edgen, p1, v1, p2, v2, (inpn == 2 ? 1 : 0)))
        return 0;
    cp1 = add (p1, scale (v1, 1 / 3.0));
    cp2 = sub (p2, scale (v2, 1 / 3.0));
    for (maxd = -1, maxi = -1, i = 1; i < inpn - 1; i++) {
        t = tnas[i].t;
        p.x = B0 (t) * p1.x + B1 (t) * cp1.x +
                B2 (t) * cp2.x + B3 (t) * p2.x;
        p.y = B0 (t) * p1.y + B1 (t) * cp1.y +
                B2 (t) * cp2.y + B3 (t) * p2.y;
        if ((d = dist (p, inps[i])) > maxd)
            maxd = d, maxi = i;
    }
    spliti = maxi;
    splitv1 = normv (sub (inps[spliti], inps[spliti - 1]));
    splitv2 = normv (sub (inps[spliti + 1], inps[spliti]));
    splitv = normv (add (splitv1, splitv2));
    reallyroutespline (edges, edgen, inps, spliti + 1, ev0, splitv);
    reallyroutespline (edges, edgen, &inps[spliti], inpn - spliti, splitv, ev1);
    return 0;
}

static int mkspline (Ppoint_t *inps, int inpn, tna_t *tnas, Ppoint_t ev0, Ppoint_t ev1,
        Ppoint_t *sp0, Ppoint_t *sv0, Ppoint_t *sp1, Ppoint_t *sv1) {
    Ppoint_t tmp;
    double c[2][2], x[2], det01, det0X, detX1;
    double d01, scale0, scale3;
    int i;

    scale0 = scale3 = 0.0;
    c[0][0] = c[0][1] = c[1][0] = c[1][1] = 0.0;
    x[0] = x[1] = 0.0;
    for (i = 0; i < inpn; i++) {
        c[0][0] += dot (tnas[i].a[0], tnas[i].a[0]);
        c[0][1] += dot (tnas[i].a[0], tnas[i].a[1]);
        c[1][0] = c[0][1];
        c[1][1] += dot (tnas[i].a[1], tnas[i].a[1]);
        tmp = sub (inps[i], add (scale (inps[0], B01 (tnas[i].t)),
                scale (inps[inpn - 1], B23 (tnas[i].t))));
        x[0] += dot (tnas[i].a[0], tmp);
        x[1] += dot (tnas[i].a[1], tmp);
    }
    det01 = c[0][0] * c[1][1] - c[1][0] * c[0][1];
    det0X = c[0][0] * x[1] - c[0][1] * x[0];
    detX1 = x[0] * c[1][1] - x[1] * c[0][1];
    if (det01 != 0.0) {
        scale0 = detX1 / det01;
        scale3 = det0X / det01;
    }
    if (ABS (det01) < 1e-6 || scale0 <= 0.0 || scale3 <= 0.0) {
        d01 = dist (inps[0], inps[inpn - 1]) / 3.0;
        scale0 = d01;
        scale3 = d01;
    }
    *sp0 = inps[0];
    *sv0 = scale (ev0, scale0);
    *sp1 = inps[inpn - 1];
    *sv1 = scale (ev1, scale3);
    return 0;
}

static int splinefits (Pedge_t *edges, int edgen, Ppoint_t pa, Pvector_t va,
        Ppoint_t pb, Pvector_t vb, int forceflag) {
    Ppoint_t sps[4];
    double a, b, d;
    int pi;

#if 0
    d = sqrt ((pb.x - pa.x) * (pb.x - pa.x) + (pb.y - pa.y) * (pb.y - pa.y));
    a = d, b = d;
#else
    a = b = 4;
#endif
    for ( ;; ) {
        sps[0].x = pa.x;
        sps[0].y = pa.y;
        sps[1].x = pa.x + a * va.x / 3.0;
        sps[1].y = pa.y + a * va.y / 3.0;
        sps[2].x = pb.x - b * vb.x / 3.0;
        sps[2].y = pb.y - b * vb.y / 3.0;
        sps[3].x = pb.x;
        sps[3].y = pb.y;
        if (splineisinside (edges, edgen, &sps[0])) {
            growops (opl + 4);
            for (pi = 1; pi < 4; pi++)
                ops[opl].x = sps[pi].x, ops[opl++].y = sps[pi].y;
#if DEBUG >= 1
fprintf (stderr, "success: %f %f\n", a, b);
#endif
            return 1;
        }
        if (a == 0 && b == 0) {
            if (forceflag) {
                growops (opl + 4);
                for (pi = 1; pi < 4; pi++)
                    ops[opl].x = sps[pi].x, ops[opl++].y = sps[pi].y;
#if DEBUG >= 1
fprintf (stderr, "forced straight line: %f %f\n", a, b);
#endif
                return 1;
            }
            break;
        }
        if (a > .01)
            a /= 2, b /= 2;
        else
            a = b = 0;
    }
#if DEBUG >= 1
fprintf (stderr, "failure\n");
#endif
    return 0;
}

static int splineisinside (Pedge_t *edges, int edgen, Ppoint_t *sps) {
    double roots[4];
    int rooti, rootn;
    int ei;
    Ppoint_t lps[2], ip;
    double t, ta, tb, tc, td;

    for (ei = 0; ei < edgen; ei++) {
        lps[0] = edges[ei].a, lps[1] = edges[ei].b;
        /* if ((rootn = splineintersectsline (sps, lps, roots)) == 4)
            return 1; */
        if ((rootn = splineintersectsline (sps, lps, roots)) == 4)
            continue;
        for (rooti = 0; rooti < rootn; rooti++) {
            if (roots[rooti] < EPSILON2 || roots[rooti] > 1 - EPSILON2)
                continue;
            t = roots[rooti];
            td = t * t * t;
            tc = 3 * t * t * (1 - t);
            tb = 3 * t * (1 - t) * (1 - t);
            ta = (1 - t) * (1 - t) * (1 - t);
            ip.x = ta * sps[0].x + tb * sps[1].x +
                    tc * sps[2].x + td * sps[3].x;
            ip.y = ta * sps[0].y + tb * sps[1].y +
                    tc * sps[2].y + td * sps[3].y;
            if (DISTSQ (ip, lps[0]) < EPSILON1 ||
                    DISTSQ (ip, lps[1]) < EPSILON1)
                continue;
            return 0;
        }
    }
    return 1;
}

static int splineintersectsline (Ppoint_t *sps, Ppoint_t *lps,
        double *roots) {
    double scoeff[4], xcoeff[2], ycoeff[2];
    double xroots[3], yroots[3], tv, sv, rat;
    int rootn, xrootn, yrootn, i, j;

    xcoeff[0] = lps[0].x;
    xcoeff[1] = lps[1].x - lps[0].x;
    ycoeff[0] = lps[0].y;
    ycoeff[1] = lps[1].y - lps[0].y;
    rootn = 0;
    if (xcoeff[1] == 0) {
        if (ycoeff[1] == 0) {
            points2coeff (sps[0].x, sps[1].x, sps[2].x, sps[3].x, scoeff);
            scoeff[0] -= xcoeff[0];
            xrootn = solve3 (scoeff, xroots);
            points2coeff (sps[0].y, sps[1].y, sps[2].y, sps[3].y, scoeff);
            scoeff[0] -= ycoeff[0];
            yrootn = solve3 (scoeff, yroots);
            if (xrootn == 4)
                if (yrootn == 4)
                    return 4;
                else
                    for (j = 0; j < yrootn; j++)
                        addroot (yroots[j], roots, &rootn);
            else if (yrootn == 4)
                for (i = 0; i < xrootn; i++)
                    addroot (xroots[i], roots, &rootn);
            else
                for (i = 0; i < xrootn; i++)
                    for (j = 0; j < yrootn; j++)
                        if (xroots[i] == yroots[j])
                            addroot (xroots[i], roots, &rootn);
            return rootn;
        } else {
            points2coeff (sps[0].x, sps[1].x, sps[2].x, sps[3].x, scoeff);
            scoeff[0] -= xcoeff[0];
            xrootn = solve3 (scoeff, xroots);
            if (xrootn == 4)
                return 4;
            for (i = 0; i < xrootn; i++) {
                tv = xroots[i];
                if (tv >= 0 && tv <= 1) {
                    points2coeff (sps[0].y, sps[1].y, sps[2].y, sps[3].y,
                            scoeff);
                    sv = scoeff[0] + tv * (scoeff[1] + tv *
                        (scoeff[2] + tv * scoeff[3]));
                    sv = (sv - ycoeff[0]) / ycoeff[1];
                    if ((0 <= sv) && (sv <= 1))
                        addroot (tv, roots, &rootn);
                }
            }
            return rootn;
        }
    } else {
        rat = ycoeff[1] / xcoeff[1];
        points2coeff (sps[0].y - rat * sps[0].x, sps[1].y - rat * sps[1].x,
                sps[2].y - rat * sps[2].x, sps[3].y - rat * sps[3].x, scoeff);
        scoeff[0] += rat * xcoeff[0] - ycoeff[0];
        xrootn = solve3 (scoeff, xroots);
        if (xrootn == 4)
            return 4;
        for (i = 0; i < xrootn; i++) {
            tv = xroots[i];
            if (tv >= 0 && tv <= 1) {
                points2coeff (sps[0].x, sps[1].x, sps[2].x, sps[3].x, scoeff);
                sv = scoeff[0] + tv * (scoeff[1] + tv *
                    (scoeff[2] + tv * scoeff[3]));
                sv = (sv - xcoeff[0]) / xcoeff[1];
                if ((0 <= sv) && (sv <= 1))
                    addroot (tv, roots, &rootn);
            }
        }
        return rootn;
    }
}

static void points2coeff (double v0, double v1, double v2, double v3,
        double *coeff) {
    coeff[3] = v3 + 3 * v1 - (v0 + 3 * v2);
    coeff[2] = 3 * v0 + 3 * v2 - 6 * v1;
    coeff[1] = 3 * (v1 - v0);
    coeff[0] = v0;
}

static void addroot (double root, double *roots, int *rootnp) {
    if (root >= 0 && root <= 1)
        roots[*rootnp] = root, (*rootnp)++;
}

static Pvector_t normv (Pvector_t v) {
    double d;

    d = sqrt (v.x * v.x + v.y * v.y);
    if (d != 0)
        v.x /= d, v.y /= d;
    return v;
}

static void growops (int newopn) {
    if (newopn <= opn)
        return;
    if (!ops) {
        if (!(ops = (Ppoint_t *) malloc (POINTSIZE * newopn))) {
            prerror ("cannot malloc ops");
            abort ();
        }
    } else {
        if (!(ops = (Ppoint_t *) realloc ((void *) ops,
                POINTSIZE * newopn))) {
            prerror ("cannot realloc ops");
            abort ();
        }
    }
    opn = newopn;
}

static Ppoint_t add (Ppoint_t p1, Ppoint_t p2) {
    p1.x += p2.x, p1.y += p2.y;
    return p1;
}

static Ppoint_t sub (Ppoint_t p1, Ppoint_t p2) {
    p1.x -= p2.x, p1.y -= p2.y;
    return p1;
}

static double dist (Ppoint_t p1, Ppoint_t p2) {
    double dx, dy;

    dx = p2.x - p1.x, dy = p2.y - p1.y;
    return sqrt (dx * dx + dy * dy);
}

static Ppoint_t scale (Ppoint_t p, double c) {
    p.x *= c, p.y *= c;
    return p;
}

static double dot (Ppoint_t p1, Ppoint_t p2) {
    return p1.x * p2.x + p1.y * p2.y;
}

static double B0 (double t)
{
    double tmp = 1.0 - t;
    return tmp * tmp * tmp;
}

static double B1 (double t)
{
    double tmp = 1.0 - t;
    return 3 * t * tmp * tmp;
}

static double B2 (double t)
{
    double tmp = 1.0 - t;
    return 3 * t * t * tmp;
}

static double B3 (double t)
{
    return t * t * t;
}

static double B01 (double t)
{
    double tmp = 1.0 - t;
    return tmp * tmp * (tmp + 3 * t);
}

static double B23 (double t)
{
    double tmp = 1.0 - t;
    return t * t * (3 * tmp + t);
}

static int cmpp2efunc (const void *v0p, const void *v1p) {
    p2e_t *p2e0p, *p2e1p;
    double x0, x1;

    p2e0p = (p2e_t *) v0p, p2e1p = (p2e_t *) v1p;
    if (p2e0p->pp->y > p2e1p->pp->y)
        return -1;
    else if (p2e0p->pp->y < p2e1p->pp->y)
        return 1;
    if (p2e0p->pp->x < p2e1p->pp->x)
        return -1;
    else if (p2e0p->pp->x > p2e1p->pp->x)
        return 1;
    x0 = (p2e0p->pp == &p2e0p->ep->a) ? p2e0p->ep->b.x : p2e0p->ep->a.x;
    x1 = (p2e1p->pp == &p2e1p->ep->a) ? p2e1p->ep->b.x : p2e1p->ep->a.x;
    if (x0 < x1)
        return -1;
    else if (x0 > x1)
        return 1;
    return 0;
}

static void listdelete (Pedge_t *ep) {
    elist_t *lp;

    for (lp = elist; lp; lp = lp->next) {
        if (lp->ep != ep)
            continue;
        if (lp->prev)
            lp->prev->next = lp->next;
        if (lp->next)
            lp->next->prev = lp->prev;
        if (elist == lp)
            elist = lp->next;
        free (lp);
        return;
    }
    if (!lp) {
        prerror ("cannot find list element to delete");
        abort ();
    }
}

static void listreplace (Pedge_t *oldep, Pedge_t *newep) {
    elist_t *lp;

    for (lp = elist; lp; lp = lp->next) {
        if (lp->ep != oldep)
            continue;
        lp->ep = newep;
        return;
    }
    if (!lp) {
        prerror ("cannot find list element to replace");
        abort ();
    }
}

static void listinsert (Pedge_t *ep, Ppoint_t p) {
    elist_t *lp, *newlp, *lastlp;
    double lx;

    if (!(newlp = (elist_t *) malloc (sizeof (elist_t)))) {
        prerror ("cannot malloc newlp");
        abort ();
    }
    newlp->ep = ep;
    newlp->next = newlp->prev = NULL;
    if (!elist) {
        elist = newlp;
        return;
    }
    for (lp = elist; lp; lp = lp->next) {
        lastlp = lp;
        lx = lp->ep->a.x + (lp->ep->b.x - lp->ep->a.x) * (p.y - lp->ep->a.y) /
                (lp->ep->b.y - lp->ep->a.y);
        if (lx <= p.x)
            continue;
        if (lp->prev)
            lp->prev->next = newlp;
        newlp->prev = lp->prev;
        newlp->next = lp;
        lp->prev = newlp;
        if (elist == lp)
            elist = newlp;
        return;
    }
    lastlp->next = newlp;
    newlp->prev = lastlp;
    if (!elist)
        elist = newlp;
}
