//-----------------------------------------------------------------------------
// Rendering projections to 2d surfaces: z-sorting, occlusion testing, etc.
//
// Copyright 2016 whitequark
//-----------------------------------------------------------------------------
#include "solvespace.h"

namespace SolveSpace {

// FIXME: The export coordinate system has a different handedness than display
// coordinate system; lighting and occlusion calculations are right-handed.
static Vector ProjectPoint3RH(const Camera &camera, Vector p) {
    p = p.Plus(camera.offset);

    Vector r;
    r.x = p.Dot(camera.projRight);
    r.y = p.Dot(camera.projUp);
    r.z = p.Dot(camera.projRight.Cross(camera.projUp));

    double w = 1 + r.z*camera.tangent*camera.scale;
    return r.ScaledBy(camera.scale/w);
}

//-----------------------------------------------------------------------------
// Accumulation of geometry
//-----------------------------------------------------------------------------

void SurfaceRenderer::DrawLine(const Vector &a, const Vector &b, hStroke hcs) {
    edges[hcs].AddEdge(ProjectPoint3RH(camera, a),
                       ProjectPoint3RH(camera, b));
}

void SurfaceRenderer::DrawEdges(const SEdgeList &el, hStroke hcs) {
    for(const SEdge &e : el.l) {
        edges[hcs].AddEdge(ProjectPoint3RH(camera, e.a),
                           ProjectPoint3RH(camera, e.b));
    }
}

bool SurfaceRenderer::DrawBeziers(const SBezierList &bl, hStroke hcs) {
    if(!CanOutputCurves())
        return false;

    for(const SBezier &b : bl.l) {
        SBezier pb = camera.ProjectBezier(b);
        beziers[hcs].l.Add(&pb);
    }
    return true;
}

void SurfaceRenderer::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) {
    Vector projDir = camera.projRight.Cross(camera.projUp);
    for(const SOutline &o : ol.l) {
        if(drawAs == DrawOutlinesAs::EMPHASIZED_AND_CONTOUR &&
                !(o.IsVisible(projDir) || o.tag != 0))
            continue;
        if(drawAs == DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR &&
                !(!o.IsVisible(projDir) && o.tag != 0))
            continue;
        if(drawAs == DrawOutlinesAs::CONTOUR_ONLY &&
                !(o.IsVisible(projDir)))
            continue;

        edges[hcs].AddEdge(ProjectPoint3RH(camera, o.a),
                           ProjectPoint3RH(camera, o.b));
    }
}

void SurfaceRenderer::DrawVectorText(const std::string &text, double height,
                                     const Vector &o, const Vector &u, const Vector &v,
                                     hStroke hcs) {
    auto traceEdge = [&](Vector a, Vector b) {
        edges[hcs].AddEdge(ProjectPoint3RH(camera, a),
                           ProjectPoint3RH(camera, b));
    };
    VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera);
}

void SurfaceRenderer::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
                               hFill hcf) {
    Fill *fill = fills.FindById(hcf);
    ssassert(fill->layer == Layer::NORMAL ||
             fill->layer == Layer::DEPTH_ONLY ||
             fill->layer == Layer::FRONT ||
             fill->layer == Layer::BACK, "Unexpected mesh layer");

    Vector zOffset = {};
    if(fill->layer == Layer::BACK) {
        zOffset.z -= 1e6;
    } else if(fill->layer == Layer::FRONT) {
        zOffset.z += 1e6;
    }
    zOffset.z += camera.scale * fill->zIndex;

    STriMeta meta = {};
    if(fill->layer != Layer::DEPTH_ONLY) {
        meta.color = fill->color;
    }
    Vector ta = ProjectPoint3RH(camera, a).Plus(zOffset),
           tb = ProjectPoint3RH(camera, b).Plus(zOffset),
           tc = ProjectPoint3RH(camera, c).Plus(zOffset),
           td = ProjectPoint3RH(camera, d).Plus(zOffset);
    mesh.AddTriangle(meta, tc, tb, ta);
    mesh.AddTriangle(meta, ta, td, tc);
}

void SurfaceRenderer::DrawPoint(const Vector &o, double s, hFill hcf) {
    Vector u = camera.projRight.ScaledBy(1 / camera.scale * s),
           v = camera.projUp.ScaledBy(1 / camera.scale * s);
    DrawQuad(o.Minus(u).Minus(v), o.Minus(u).Plus(v),
             o.Plus(u).Plus(v),   o.Plus(u).Minus(v), hcf);
}

void SurfaceRenderer::DrawPolygon(const SPolygon &p, hFill hcf) {
    SMesh m = {};
    p.TriangulateInto(&m);
    DrawMesh(m, hcf, {}, {});
    m.Clear();
}

void SurfaceRenderer::DrawMesh(const SMesh &m,
                               hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) {
    Fill *fill = fills.FindById(hcfFront);
    ssassert(fill->layer == Layer::NORMAL ||
             fill->layer == Layer::DEPTH_ONLY, "Unexpected mesh layer");

    Vector l0 = (lighting.lightDirection[0]).WithMagnitude(1),
           l1 = (lighting.lightDirection[1]).WithMagnitude(1);
    for(STriangle tr : m.l) {
        tr.a = ProjectPoint3RH(camera, tr.a);
        tr.b = ProjectPoint3RH(camera, tr.b);
        tr.c = ProjectPoint3RH(camera, tr.c);

        if(CanOutputTriangles() && fill->layer == Layer::NORMAL) {
            if(fill->color.IsEmpty()) {
                // Compute lighting, since we're going to draw the shaded triangles.
                Vector n = tr.Normal().WithMagnitude(1);
                double intensity = lighting.ambientIntensity +
                                        max(0.0, (lighting.lightIntensity[0])*(n.Dot(l0))) +
                                        max(0.0, (lighting.lightIntensity[1])*(n.Dot(l1)));
                double r = min(1.0, tr.meta.color.redF()   * intensity),
                       g = min(1.0, tr.meta.color.greenF() * intensity),
                       b = min(1.0, tr.meta.color.blueF()  * intensity);
                tr.meta.color = RGBf(r, g, b);
            } else {
                // We're going to draw this triangle, but it's not shaded.
                tr.meta.color = fill->color;
            }
        } else {
            // This triangle is just for occlusion testing.
            tr.meta.color = {};
        }
        mesh.AddTriangle(&tr);
    }

    if(hcsTriangles.v != 0) {
        for(const STriangle &tr : m.l) {
            edges[hcsTriangles].AddEdge(ProjectPoint3RH(camera, tr.a),
                                        ProjectPoint3RH(camera, tr.b));
            edges[hcsTriangles].AddEdge(ProjectPoint3RH(camera, tr.b),
                                        ProjectPoint3RH(camera, tr.c));
            edges[hcsTriangles].AddEdge(ProjectPoint3RH(camera, tr.c),
                                        ProjectPoint3RH(camera, tr.a));
        }
    }
}

void SurfaceRenderer::DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) {
    Fill *fill = fills.FindById(hcf);
    ssassert(fill->layer == Layer::NORMAL ||
             fill->layer == Layer::DEPTH_ONLY, "Unexpected mesh layer");

    Vector zOffset = {};
    zOffset.z += camera.scale * fill->zIndex;

    size_t facesSize = faces.size();
    for(STriangle tr : m.l) {
        uint32_t face = tr.meta.face;
        for(size_t j = 0; j < facesSize; j++) {
            if(faces[j] != face) continue;
            if(!fill->color.IsEmpty()) {
                tr.meta.color = fill->color;
            }
            mesh.AddTriangle(tr.meta,
                ProjectPoint3RH(camera, tr.a).Plus(zOffset),
                ProjectPoint3RH(camera, tr.b).Plus(zOffset),
                ProjectPoint3RH(camera, tr.c).Plus(zOffset));
            break;
        }
    }
}

void SurfaceRenderer::DrawPixmap(std::shared_ptr<const Pixmap> pm,
                    const Vector &o, const Vector &u, const Vector &v,
                    const Point2d &ta, const Point2d &tb, hFill hcf) {
    ssassert(false, "Not implemented");
}

void SurfaceRenderer::InvalidatePixmap(std::shared_ptr<const Pixmap> pm) {
    ssassert(false, "Not implemented");
}

//-----------------------------------------------------------------------------
// Processing of geometry
//-----------------------------------------------------------------------------

void SurfaceRenderer::CalculateBBox() {
    bbox.minp = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE);
    bbox.maxp = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE);

    for(auto &it : edges) {
        SEdgeList &el = it.second;
        for(SEdge &e : el.l) {
            bbox.Include(e.a);
            bbox.Include(e.b);
        }
    }

    for(auto &it : beziers) {
        SBezierList &bl = it.second;
        for(SBezier &b : bl.l) {
            for(int i = 0; i <= b.deg; i++) {
                bbox.Include(b.ctrl[i]);
            }
        }
    }

    for(STriangle &tr : mesh.l) {
        for(int i = 0; i < 3; i++) {
            bbox.Include(tr.vertices[i]);
        }
    }
}


void SurfaceRenderer::ConvertBeziersToEdges() {
    for(auto &it : beziers) {
        hStroke hcs = it.first;
        SBezierList &bl = it.second;

        SEdgeList &el = edges[hcs];
        for(const SBezier &b : bl.l) {
            if(b.deg == 1) {
                el.AddEdge(b.ctrl[0], b.ctrl[1]);
            } else {
                List<Vector> lv = {};
                b.MakePwlInto(&lv, chordTolerance);
                for(int i = 1; i < lv.n; i++) {
                    el.AddEdge(lv.elem[i-1], lv.elem[i]);
                }
                lv.Clear();
            }
        }
        bl.l.Clear();
    }
    beziers.clear();
}

void SurfaceRenderer::CullOccludedStrokes() {
    // Perform occlusion testing, if necessary.
    if(mesh.l.n == 0) return;

    // We can't perform hidden line removal on exact curves.
    ConvertBeziersToEdges();

    // Remove hidden lines (on NORMAL layers), or remove visible lines (on OCCLUDED layers).
    SKdNode *root = SKdNode::From(&mesh);
    root->ClearTags();

    int cnt = 1234;
    for(auto &eit : edges) {
        hStroke hcs = eit.first;
        SEdgeList &el = eit.second;

        Stroke *stroke = strokes.FindById(hcs);
        if(stroke->layer != Layer::NORMAL &&
           stroke->layer != Layer::OCCLUDED) continue;

        SEdgeList nel = {};
        for(const SEdge &e : el.l) {
            SEdgeList oel = {};
            oel.AddEdge(e.a, e.b);
            root->OcclusionTestLine(e, &oel, cnt);

            if(stroke->layer == Layer::OCCLUDED) {
                for(SEdge &oe : oel.l) {
                    oe.tag = !oe.tag;
                }
            }
            oel.l.RemoveTagged();

            oel.MergeCollinearSegments(e.a, e.b);
            for(const SEdge &oe : oel.l) {
                nel.AddEdge(oe.a, oe.b);
            }

            oel.Clear();
            cnt++;
        }

        el.l.Clear();
        el.l = nel.l;
    }
}

void SurfaceRenderer::OutputInPaintOrder() {
    // Sort our strokes in paint order.
    std::vector<std::pair<Layer, int>> paintOrder;
    paintOrder.emplace_back(Layer::NORMAL, 0); // mesh
    for(const Stroke &cs : strokes) {
        paintOrder.emplace_back(cs.layer, cs.zIndex);
    }

    const Layer stackup[] = {
        Layer::BACK, Layer::NORMAL, Layer::DEPTH_ONLY, Layer::OCCLUDED, Layer::FRONT
    };
    std::sort(paintOrder.begin(), paintOrder.end(),
              [&](std::pair<Layer, int> a, std::pair<Layer, int> b) {
        Layer aLayer  = a.first,
              bLayer  = b.first;
        int   aZIndex = a.second,
              bZIndex = b.second;

        int aLayerIndex =
            std::find(std::begin(stackup), std::end(stackup), aLayer) - std::begin(stackup);
        int bLayerIndex =
            std::find(std::begin(stackup), std::end(stackup), bLayer) - std::begin(stackup);
        if(aLayerIndex == bLayerIndex) {
            return aZIndex < bZIndex;
        } else {
            return aLayerIndex < bLayerIndex;
        }
    });

    auto last = std::unique(paintOrder.begin(), paintOrder.end());
    paintOrder.erase(last, paintOrder.end());

    // Output geometry in paint order.
    OutputStart();
    for(auto &it : paintOrder) {
        Layer layer  = it.first;
        int   zIndex = it.second;

        if(layer == Layer::NORMAL && zIndex == 0) {
            SMesh mp = {};
            SBsp3 *bsp = SBsp3::FromMesh(&mesh);
            if(bsp) bsp->GenerateInPaintOrder(&mp);

            for(const STriangle &tr : mp.l) {
                // Cull back-facing and invisible triangles.
                if(tr.Normal().z < 0) continue;
                if(tr.meta.color.IsEmpty()) continue;
                OutputTriangle(tr);
            }
        }

        for(auto eit : edges) {
            hStroke hcs = eit.first;
            const SEdgeList &el = eit.second;

            Stroke *stroke = strokes.FindById(hcs);
            if(stroke->layer != layer || stroke->zIndex != zIndex) continue;

            for(const SEdge &e : el.l) {
                OutputBezier(SBezier::From(e.a, e.b), hcs);
            }
        }

        for(auto &bit : beziers) {
            hStroke hcs = bit.first;
            const SBezierList &bl = bit.second;

            Stroke *stroke = strokes.FindById(hcs);
            if(stroke->layer != layer || stroke->zIndex != zIndex) continue;

            for(const SBezier &b : bl.l) {
                OutputBezier(b, hcs);
            }
        }
    }
    OutputEnd();
}

void SurfaceRenderer::Clear() {
    for(auto &eit : edges) {
        SEdgeList &el = eit.second;
        el.l.Clear();
    }
    edges.clear();

    for(auto &bit : beziers) {
        SBezierList &bl = bit.second;
        bl.l.Clear();
    }
    beziers.clear();

    mesh.Clear();
}

void SurfaceRenderer::OutputBezierAsNonrationalCubic(const SBezier &b, hStroke hcs) {
    // Arbitrary choice of tolerance; make it a little finer than pwl tolerance since
    // it should be easier to achieve that with the smooth curves.
    SBezierList bl;
    b.MakeNonrationalCubicInto(&bl, chordTolerance / 2);
    for(const SBezier &cb : bl.l) {
        OutputBezier(cb, hcs);
    }
    bl.Clear();
}

}
