#include "solvespace.h"

// A public-domain Hershey vector font ("Simplex").
#include "font.table"

static bool ColorLocked;
static bool DepthOffsetLocked;

#define FONT_SCALE(h) ((h)/22.0)
double glxStrWidth(char *str, double h) {
    int w = 0;
    for(; *str; str++) {
        int c = *str;
        if(c < 32 || c > 126) c = 32;
        c -= 32;

        w += Font[c].width;
    }
    return w*FONT_SCALE(h)/SS.GW.scale;
}
double glxStrHeight(double h) {
    // The characters have height ~22, as they appear in the table.
    return 22.0*FONT_SCALE(h)/SS.GW.scale;
}
void glxWriteTextRefCenter(char *str, double h, Vector t, Vector u, Vector v, 
                                glxLineFn *fn, void *fndata)
{
    u = u.WithMagnitude(1);
    v = v.WithMagnitude(1);

    double scale = FONT_SCALE(h)/SS.GW.scale;
    double fh = glxStrHeight(h);
    double fw = glxStrWidth(str, h);

    t = t.Plus(u.ScaledBy(-fw/2));
    t = t.Plus(v.ScaledBy(-fh/2));

    // Undo the (+5, +5) offset that glxWriteText applies.
    t = t.Plus(u.ScaledBy(-5*scale));
    t = t.Plus(v.ScaledBy(-5*scale));

    glxWriteText(str, h, t, u, v, fn, fndata);
}

static void LineDrawCallback(void *fndata, Vector a, Vector b)
{
    glLineWidth(1);
    glBegin(GL_LINES);
        glxVertex3v(a);
        glxVertex3v(b);
    glEnd();
}

void glxWriteText(char *str, double h, Vector t, Vector u, Vector v,
                    glxLineFn *fn, void *fndata)
{
    if(!fn) fn = LineDrawCallback;
    u = u.WithMagnitude(1);
    v = v.WithMagnitude(1);

    double scale = FONT_SCALE(h)/SS.GW.scale;
    int xo = 5;
    int yo = 5;
    
    for(; *str; str++) {
        int c = *str;
        if(c < 32 || c > 126) c = 32;

        c -= 32;

        int j;
        Vector prevp = Vector::From(VERY_POSITIVE, 0, 0);
        for(j = 0; j < Font[c].points; j++) {
            int x = Font[c].coord[j*2];
            int y = Font[c].coord[j*2+1];

            if(x == PEN_UP && y == PEN_UP) {
                prevp.x = VERY_POSITIVE;
            } else {
                Vector p = t;
                p = p.Plus(u.ScaledBy((xo + x)*scale));
                p = p.Plus(v.ScaledBy((yo + y)*scale));
                if(prevp.x != VERY_POSITIVE) {
                    fn(fndata, prevp, p);
                }
                prevp = p;
            }
        }

        xo += Font[c].width;
    }
}

void glxVertex3v(Vector u)
{
    glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z);
}

static void FatLineEndcap(Vector p, Vector u, Vector v)
{
    // A table of cos and sin of (pi*i/10 + pi/2), as i goes from 0 to 10
    static const double Circle[11][2] = {
        {  0.0000,   1.0000 },
        { -0.3090,   0.9511 },
        { -0.5878,   0.8090 },
        { -0.8090,   0.5878 },
        { -0.9511,   0.3090 },
        { -1.0000,   0.0000 },
        { -0.9511,  -0.3090 },
        { -0.8090,  -0.5878 },
        { -0.5878,  -0.8090 },
        { -0.3090,  -0.9511 },
        {  0.0000,  -1.0000 },
    };
    glBegin(GL_TRIANGLE_FAN);
    for(int i = 0; i <= 10; i++) {
        double c = Circle[i][0], s = Circle[i][1];
        glxVertex3v(p.Plus(u.ScaledBy(c)).Plus(v.ScaledBy(s)));
    }
    glEnd();
}

void glxFatLine(Vector a, Vector b, double width) {
    // The half-width of the line we're drawing.
    double hw = width / 2;
    Vector ab  = b.Minus(a);
    Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
    Vector abn = (ab.Cross(gn)).WithMagnitude(1);
    abn = abn.Minus(gn.ScaledBy(gn.Dot(abn)));
    // So now abn is normal to the projection of ab into the screen, so the
    // line will always have constant thickness as the view is rotated.

    abn = abn.WithMagnitude(hw);
    ab  = gn.Cross(abn);
    ab  = ab. WithMagnitude(hw);

    // The body of a line is a quad
    glBegin(GL_QUADS);
        glxVertex3v(a.Minus(abn));
        glxVertex3v(b.Minus(abn));
        glxVertex3v(b.Plus (abn));
        glxVertex3v(a.Plus (abn));
    glEnd();
    // And the line has two semi-circular end caps.
    FatLineEndcap(a, ab,              abn);
    FatLineEndcap(b, ab.ScaledBy(-1), abn);
}


void glxLockColorTo(DWORD rgb)
{
    ColorLocked = false;
    glColor3d(REDf(rgb), GREENf(rgb), BLUEf(rgb));
    ColorLocked = true;
}

void glxUnlockColor(void)
{
    ColorLocked = false;
}

void glxColorRGB(DWORD rgb)
{
    // Is there a bug in some graphics drivers where this is not equivalent
    // to glColor3d? There seems to be...
    glxColorRGBa(rgb, 1.0);
}

void glxColorRGBa(DWORD rgb, double a)
{
    if(!ColorLocked) glColor4d(REDf(rgb), GREENf(rgb), BLUEf(rgb), a);
}

static void Stipple(BOOL forSel)
{
    static BOOL Init;
    const int BYTES = (32*32)/8;
    static GLubyte HoverMask[BYTES];
    static GLubyte SelMask[BYTES];
    if(!Init) {
        int x, y;
        for(x = 0; x < 32; x++) {
            for(y = 0; y < 32; y++) {
                int i = y*4 + x/8, b = x % 8;
                int ym = y % 4, xm = x % 4;
                for(int k = 0; k < 2; k++) {
                    if(xm >= 1 && xm <= 2 && ym >= 1 && ym <= 2) {
                        (k == 0 ? SelMask : HoverMask)[i] |= (0x80 >> b);
                    }
                    ym = (ym + 2) % 4; xm = (xm + 2) % 4;
                }
            }
        }
        Init = TRUE;
    }

    glEnable(GL_POLYGON_STIPPLE);
    if(forSel) {
        glPolygonStipple(SelMask);
    } else {
        glPolygonStipple(HoverMask);
    }
}

static void StippleTriangle(STriangle *tr, BOOL s, DWORD rgb)
{
    glEnd();
    glDisable(GL_LIGHTING);
    glxColorRGB(rgb);
    Stipple(s);
    glBegin(GL_TRIANGLES);
        glxVertex3v(tr->a);
        glxVertex3v(tr->b);
        glxVertex3v(tr->c);
    glEnd();
    glEnable(GL_LIGHTING);
    glDisable(GL_POLYGON_STIPPLE);
    glBegin(GL_TRIANGLES);
}

void glxFillMesh(int specColor, SMesh *m, DWORD h, DWORD s1, DWORD s2)
{
    DWORD rgbHovered  = Style::Color(Style::HOVERED),
          rgbSelected = Style::Color(Style::SELECTED);

    glEnable(GL_NORMALIZE);
    int prevColor = -1;
    glBegin(GL_TRIANGLES);
    for(int i = 0; i < m->l.n; i++) {
        STriangle *tr = &(m->l.elem[i]);

        int color;
        if(specColor < 0) {
            color = tr->meta.color;
        } else {
            color = specColor;
        }
        if(color != prevColor) {
            GLfloat mpf[] = { REDf(color), GREENf(color), BLUEf(color), 1.0 };
            glEnd();
            glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mpf);
            prevColor = color;
            glBegin(GL_TRIANGLES);
        }

        if(0 || tr->an.EqualsExactly(Vector::From(0, 0, 0))) {
            // Compute the normal from the vertices
            Vector n = tr->Normal();
            glNormal3d(n.x, n.y, n.z);
            glxVertex3v(tr->a);
            glxVertex3v(tr->b);
            glxVertex3v(tr->c);
        } else {
            // Use the exact normals that are specified
            glNormal3d((tr->an).x, (tr->an).y, (tr->an).z);
            glxVertex3v(tr->a);

            glNormal3d((tr->bn).x, (tr->bn).y, (tr->bn).z);
            glxVertex3v(tr->b);

            glNormal3d((tr->cn).x, (tr->cn).y, (tr->cn).z);
            glxVertex3v(tr->c);
        }

        if((s1 != 0 && tr->meta.face == s1) || 
           (s2 != 0 && tr->meta.face == s2))
        {
            StippleTriangle(tr, TRUE, rgbSelected);
        }
        if(h != 0 && tr->meta.face == h) {
            StippleTriangle(tr, FALSE, rgbHovered);
        }
    }
    glEnd();
}

static void GLX_CALLBACK Vertex(Vector *p) {
    glxVertex3v(*p);
}

void glxFillPolygon(SPolygon *p)
{
    GLUtesselator *gt = gluNewTess();
    gluTessCallback(gt, GLU_TESS_BEGIN, (glxCallbackFptr *)glBegin);
    gluTessCallback(gt, GLU_TESS_END, (glxCallbackFptr *)glEnd);
    gluTessCallback(gt, GLU_TESS_VERTEX, (glxCallbackFptr *)Vertex);

    glxTesselatePolygon(gt, p);

    gluDeleteTess(gt);
}

static void GLX_CALLBACK Combine(double coords[3], void *vertexData[4],
                                 float weight[4], void **outData)
{
    Vector *n = (Vector *)AllocTemporary(sizeof(Vector));
    n->x = coords[0];
    n->y = coords[1];
    n->z = coords[2];

    *outData = n;
}
void glxTesselatePolygon(GLUtesselator *gt, SPolygon *p)
{
    int i, j;

    gluTessCallback(gt, GLU_TESS_COMBINE, (glxCallbackFptr *)Combine);
    gluTessProperty(gt, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);

    Vector normal = p->normal;
    glNormal3d(normal.x, normal.y, normal.z);
    gluTessNormal(gt, normal.x, normal.y, normal.z);

    gluTessBeginPolygon(gt, NULL);
    for(i = 0; i < p->l.n; i++) {
        SContour *sc = &(p->l.elem[i]);
        gluTessBeginContour(gt);
        for(j = 0; j < (sc->l.n-1); j++) {
            SPoint *sp = &(sc->l.elem[j]);
            double ap[3];
            ap[0] = sp->p.x;
            ap[1] = sp->p.y;
            ap[2] = sp->p.z;
            gluTessVertex(gt, ap, &(sp->p));
        }
        gluTessEndContour(gt);
    }
    gluTessEndPolygon(gt);
}

void glxDebugPolygon(SPolygon *p)
{
    int i, j;
    glLineWidth(2);
    glPointSize(7);
    glDisable(GL_DEPTH_TEST);
    for(i = 0; i < p->l.n; i++) {
        SContour *sc = &(p->l.elem[i]);
        for(j = 0; j < (sc->l.n-1); j++) {
            Vector a = (sc->l.elem[j]).p;
            Vector b = (sc->l.elem[j+1]).p;

            glxLockColorTo(RGB(0, 0, 255));
            Vector d = (a.Minus(b)).WithMagnitude(-0);
            glBegin(GL_LINES);
                glxVertex3v(a.Plus(d));
                glxVertex3v(b.Minus(d));
            glEnd();
            glxLockColorTo(RGB(255, 0, 0));
            glBegin(GL_POINTS);
                glxVertex3v(a.Plus(d));
                glxVertex3v(b.Minus(d));
            glEnd();
        }
    }
}

void glxDrawEdges(SEdgeList *el, bool endpointsToo)
{
    SEdge *se;
    glBegin(GL_LINES);
    for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
        glxVertex3v(se->a);
        glxVertex3v(se->b);
    }
    glEnd();

    if(endpointsToo) {
        glPointSize(12);
        glBegin(GL_POINTS);
        for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
            glxVertex3v(se->a);
            glxVertex3v(se->b);
        }
        glEnd();
    }
}

void glxDebugMesh(SMesh *m)
{
    int i;
    glLineWidth(1);
    glPointSize(7);
    glxDepthRangeOffset(1);
    glxUnlockColor();
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glxColorRGBa(RGB(0, 255, 0), 1.0);
    glBegin(GL_TRIANGLES);
    for(i = 0; i < m->l.n; i++) {
        STriangle *t = &(m->l.elem[i]);
        if(t->tag) continue;

        glxVertex3v(t->a);
        glxVertex3v(t->b);
        glxVertex3v(t->c);
    }
    glEnd();
    glxDepthRangeOffset(0);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

void glxMarkPolygonNormal(SPolygon *p)
{
    Vector tail = Vector::From(0, 0, 0);
    int i, j, cnt = 0;
    // Choose some reasonable center point.
    for(i = 0; i < p->l.n; i++) {
        SContour *sc = &(p->l.elem[i]);
        for(j = 0; j < (sc->l.n-1); j++) {
            SPoint *sp = &(sc->l.elem[j]);
            tail = tail.Plus(sp->p);
            cnt++;
        }
    }
    if(cnt == 0) return;
    tail = tail.ScaledBy(1.0/cnt);

    Vector gn = SS.GW.projRight.Cross(SS.GW.projUp);
    Vector tip = tail.Plus((p->normal).WithMagnitude(40/SS.GW.scale));
    Vector arrow = (p->normal).WithMagnitude(15/SS.GW.scale);

    glColor3d(1, 1, 0);
    glBegin(GL_LINES);
        glxVertex3v(tail);
        glxVertex3v(tip);
        glxVertex3v(tip);
        glxVertex3v(tip.Minus(arrow.RotatedAbout(gn, 0.6)));
        glxVertex3v(tip);
        glxVertex3v(tip.Minus(arrow.RotatedAbout(gn, -0.6)));
    glEnd();
    glEnable(GL_LIGHTING);
}

void glxDepthRangeOffset(int units) {
    if(!DepthOffsetLocked) {
        // The size of this step depends on the resolution of the Z buffer; for
        // a 16-bit buffer, this should be fine.
        double d = units/60000.0;
        glDepthRange(0.1-d, 1-d);
    }
}

void glxDepthRangeLockToFront(bool yes) {
    if(yes) {
        DepthOffsetLocked = true;
        glDepthRange(0, 0);
    } else {
        DepthOffsetLocked = false;
        glxDepthRangeOffset(0);
    }
}

