#include "solvespace.h"

//-----------------------------------------------------------------------------
// Replace a point-coincident constraint on oldpt with that same constraint
// on newpt. Useful when splitting or tangent arcing.
//-----------------------------------------------------------------------------
void GraphicsWindow::ReplacePointInConstraints(hEntity oldpt, hEntity newpt) {
    int i;
    for(i = 0; i < SK.constraint.n; i++) {
        Constraint *c = &(SK.constraint.elem[i]);

        if(c->type == Constraint::POINTS_COINCIDENT) {
            if(c->ptA.v == oldpt.v) c->ptA = newpt;
            if(c->ptB.v == oldpt.v) c->ptB = newpt;
        }
    }
}

void GraphicsWindow::FixConstraintsForRequestBeingDeleted(hRequest hr) {
    Request *r = SK.GetRequest(hr);
    if(r->group.v != SS.GW.activeGroup.v) return;

    Entity *e;
    for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
        if(!(e->h.isFromRequest())) continue;
        if(e->h.request().v != hr.v) continue;

        if(e->type != Entity::POINT_IN_2D &&
           e->type != Entity::POINT_IN_3D)
        {
            continue;
        }

        // This is a point generated by the request being deleted; so fix
        // the constraints for that.
        FixConstraintsForPointBeingDeleted(e->h);
    }
}
void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) {
    List<hEntity> ld;
    ZERO(&ld);

    Constraint *c;
    SK.constraint.ClearTags();
    for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) {
        if(c->type != Constraint::POINTS_COINCIDENT) continue;
        if(c->group.v != SS.GW.activeGroup.v) continue;

        if(c->ptA.v == hpt.v) {
            ld.Add(&(c->ptB));
            c->tag = 1;
        }
        if(c->ptB.v == hpt.v) {
            ld.Add(&(c->ptA));
            c->tag = 1;
        }
    }
    // These would get removed anyways when we regenerated, but do it now;
    // that way subsequent calls of this function (if multiple coincident
    // points are getting deleted) will work correctly.
    SK.constraint.RemoveTagged();

    // If more than one point was constrained coincident with hpt, then
    // those two points were implicitly coincident with each other. By
    // deleting hpt (and all constraints that mention it), we will delete
    // that relationship. So put it back here now.
    int i;
    for(i = 1; i < ld.n; i++) {
        Constraint::ConstrainCoincident(ld.elem[i-1], ld.elem[i]);
    }
    ld.Clear();
}

//-----------------------------------------------------------------------------
// A single point must be selected when this function is called. We find two
// non-construction line segments that join at this point, and create a
// tangent arc joining them.
//-----------------------------------------------------------------------------
void GraphicsWindow::MakeTangentArc(void) {
    if(!LockedInWorkplane()) {
        Error("Must be sketching in workplane to create tangent "
              "arc.");
        return;
    }

    // Find two line segments that join at the specified point,
    // and blend them with a tangent arc. First, find the
    // requests that generate the line segments.
    Vector pshared = SK.GetEntity(gs.point[0])->PointGetNum();
    ClearSelection();

    int i, c = 0;
    Entity *line[2];
    Request *lineReq[2];
    bool point1[2];
    for(i = 0; i < SK.request.n; i++) {
        Request *r = &(SK.request.elem[i]);
        if(r->group.v != activeGroup.v) continue;
        if(r->type != Request::LINE_SEGMENT) continue;
        if(r->construction) continue;

        Entity *e = SK.GetEntity(r->h.entity(0));
        Vector p0 = SK.GetEntity(e->point[0])->PointGetNum(),
               p1 = SK.GetEntity(e->point[1])->PointGetNum();
        
        if(p0.Equals(pshared) || p1.Equals(pshared)) {
            if(c < 2) {
                line[c] = e;
                lineReq[c] = r;
                point1[c] = (p1.Equals(pshared));
            }
            c++;
        }
    }
    if(c != 2) {
        Error("To create a tangent arc, select a point where "
              "two non-construction line segments join.");
        return;
    }

    SS.UndoRemember();

    Entity *wrkpl = SK.GetEntity(ActiveWorkplane());
    Vector wn = wrkpl->Normal()->NormalN();

    hEntity hshared = (line[0])->point[point1[0] ? 1 : 0],
            hother0 = (line[0])->point[point1[0] ? 0 : 1],
            hother1 = (line[1])->point[point1[1] ? 0 : 1];

    Vector pother0 = SK.GetEntity(hother0)->PointGetNum(),
           pother1 = SK.GetEntity(hother1)->PointGetNum();

    Vector v0shared = pshared.Minus(pother0),
           v1shared = pshared.Minus(pother1);

    hEntity srcline0 = (line[0])->h,
            srcline1 = (line[1])->h;

    (lineReq[0])->construction = true;
    (lineReq[1])->construction = true;

    // And thereafter we mustn't touch the entity or req ptrs,
    // because the new requests/entities we add might force a
    // realloc.
    memset(line, 0, sizeof(line));
    memset(lineReq, 0, sizeof(lineReq));

    // The sign of vv determines whether shortest distance is
    // clockwise or anti-clockwise.
    Vector v = (wn.Cross(v0shared)).WithMagnitude(1);
    double vv = v1shared.Dot(v);

    double dot = (v0shared.WithMagnitude(1)).Dot(
                  v1shared.WithMagnitude(1));
    double theta = acos(dot);
    double r = 200/scale;
    // Set the radius so that no more than one third of the 
    // line segment disappears.
    r = min(r, v0shared.Magnitude()*tan(theta/2)/3);
    r = min(r, v1shared.Magnitude()*tan(theta/2)/3);
    double el = r/tan(theta/2);

    hRequest rln0 = AddRequest(Request::LINE_SEGMENT, false),
             rln1 = AddRequest(Request::LINE_SEGMENT, false);
    hRequest rarc = AddRequest(Request::ARC_OF_CIRCLE, false);

    Entity *ln0 = SK.GetEntity(rln0.entity(0)),
           *ln1 = SK.GetEntity(rln1.entity(0));
    Entity *arc = SK.GetEntity(rarc.entity(0));

    SK.GetEntity(ln0->point[0])->PointForceTo(pother0);
    Constraint::ConstrainCoincident(ln0->point[0], hother0);

    SK.GetEntity(ln1->point[0])->PointForceTo(pother1);
    Constraint::ConstrainCoincident(ln1->point[0], hother1);

    Vector arc0 = pshared.Minus(v0shared.WithMagnitude(el));
    Vector arc1 = pshared.Minus(v1shared.WithMagnitude(el));

    SK.GetEntity(ln0->point[1])->PointForceTo(arc0);
    SK.GetEntity(ln1->point[1])->PointForceTo(arc1);

    Constraint::Constrain(Constraint::PT_ON_LINE,
        ln0->point[1], Entity::NO_ENTITY, srcline0);
    Constraint::Constrain(Constraint::PT_ON_LINE,
        ln1->point[1], Entity::NO_ENTITY, srcline1);

    Vector center = arc0;
    int a, b;
    if(vv < 0) {
        a = 1; b = 2;
        center = center.Minus(v0shared.Cross(wn).WithMagnitude(r));
    } else {
        a = 2; b = 1;
        center = center.Plus(v0shared.Cross(wn).WithMagnitude(r));
    }

    SK.GetEntity(arc->point[0])->PointForceTo(center);
    SK.GetEntity(arc->point[a])->PointForceTo(arc0);
    SK.GetEntity(arc->point[b])->PointForceTo(arc1);

    Constraint::ConstrainCoincident(arc->point[a], ln0->point[1]);
    Constraint::ConstrainCoincident(arc->point[b], ln1->point[1]);

    Constraint::Constrain(Constraint::ARC_LINE_TANGENT,
        Entity::NO_ENTITY, Entity::NO_ENTITY,
        arc->h, ln0->h, (a==2));
    Constraint::Constrain(Constraint::ARC_LINE_TANGENT,
        Entity::NO_ENTITY, Entity::NO_ENTITY,
        arc->h, ln1->h, (b==2));

    SS.later.generateAll = true;
}

hEntity GraphicsWindow::SplitLine(hEntity he, Vector pinter) {
    // Save the original endpoints, since we're about to delete this entity.
    Entity *e01 = SK.GetEntity(he);
    hEntity hep0 = e01->point[0], hep1 = e01->point[1];
    Vector p0 = SK.GetEntity(hep0)->PointGetNum(),
           p1 = SK.GetEntity(hep1)->PointGetNum();

    // Add the two line segments this one gets split into.
    hRequest r0i = AddRequest(Request::LINE_SEGMENT, false),
             ri1 = AddRequest(Request::LINE_SEGMENT, false);
    // Don't get entities till after adding, realloc issues

    Entity *e0i = SK.GetEntity(r0i.entity(0)),
           *ei1 = SK.GetEntity(ri1.entity(0));

    SK.GetEntity(e0i->point[0])->PointForceTo(p0);
    SK.GetEntity(e0i->point[1])->PointForceTo(pinter);
    SK.GetEntity(ei1->point[0])->PointForceTo(pinter);
    SK.GetEntity(ei1->point[1])->PointForceTo(p1);

    ReplacePointInConstraints(hep0, e0i->point[0]);
    ReplacePointInConstraints(hep1, ei1->point[1]);
    Constraint::ConstrainCoincident(e0i->point[1], ei1->point[0]);
    return e0i->point[1];
}

hEntity GraphicsWindow::SplitCircle(hEntity he, Vector pinter) {
    Entity *circle = SK.GetEntity(he);
    if(circle->type == Entity::CIRCLE) {
        // Start with an unbroken circle, split it into a 360 degree arc.
        Vector center = SK.GetEntity(circle->point[0])->PointGetNum();

        circle = NULL; // shortly invalid!
        hRequest hr = AddRequest(Request::ARC_OF_CIRCLE, false);

        Entity *arc = SK.GetEntity(hr.entity(0));

        SK.GetEntity(arc->point[0])->PointForceTo(center);
        SK.GetEntity(arc->point[1])->PointForceTo(pinter);
        SK.GetEntity(arc->point[2])->PointForceTo(pinter);

        Constraint::ConstrainCoincident(arc->point[1], arc->point[2]);
        return arc->point[1];
    } else {
        // Start with an arc, break it in to two arcs
        hEntity hc = circle->point[0],
                hs = circle->point[1],
                hf = circle->point[2];
        Vector center = SK.GetEntity(hc)->PointGetNum(),
               start  = SK.GetEntity(hs)->PointGetNum(),
               finish = SK.GetEntity(hf)->PointGetNum();

        circle = NULL; // shortly invalid!
        hRequest hr0 = AddRequest(Request::ARC_OF_CIRCLE, false),
                 hr1 = AddRequest(Request::ARC_OF_CIRCLE, false);

        Entity *arc0 = SK.GetEntity(hr0.entity(0)),
               *arc1 = SK.GetEntity(hr1.entity(0));

        SK.GetEntity(arc0->point[0])->PointForceTo(center);
        SK.GetEntity(arc0->point[1])->PointForceTo(start);
        SK.GetEntity(arc0->point[2])->PointForceTo(pinter);

        SK.GetEntity(arc1->point[0])->PointForceTo(center);
        SK.GetEntity(arc1->point[1])->PointForceTo(pinter);
        SK.GetEntity(arc1->point[2])->PointForceTo(finish);

        ReplacePointInConstraints(hs, arc0->point[1]);
        ReplacePointInConstraints(hf, arc1->point[2]);
        Constraint::ConstrainCoincident(arc0->point[2], arc1->point[1]);
        return arc0->point[2];
    }
}

hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) {
    // Save the original endpoints, since we're about to delete this entity.
    Entity *e01 = SK.GetEntity(he);
    SBezierList sbl;
    ZERO(&sbl);
    e01->GenerateBezierCurves(&sbl);

    hEntity hep0 = e01->point[0],
            hep1 = e01->point[3+e01->extraPoints],
            hep0n = Entity::NO_ENTITY, // the new start point
            hep1n = Entity::NO_ENTITY, // the new finish point
            hepin = Entity::NO_ENTITY; // the intersection point

    // The curve may consist of multiple cubic segments. So find which one
    // contains the intersection point.
    double t;
    int i, j;
    for(i = 0; i < sbl.l.n; i++) {
        SBezier *sb = &(sbl.l.elem[i]);
        if(sb->deg != 3) oops();

        sb->ClosestPointTo(pinter, &t, false);
        if(pinter.Equals(sb->PointAt(t))) {
            // Split that segment at the intersection.
            SBezier b0i, bi1, b01 = *sb;
            b01.SplitAt(t, &b0i, &bi1);

            // Add the two cubic segments this one gets split into.
            hRequest r0i = AddRequest(Request::CUBIC, false),
                     ri1 = AddRequest(Request::CUBIC, false);
            // Don't get entities till after adding, realloc issues

            Entity *e0i = SK.GetEntity(r0i.entity(0)),
                   *ei1 = SK.GetEntity(ri1.entity(0));

            for(j = 0; j <= 3; j++) {
                SK.GetEntity(e0i->point[j])->PointForceTo(b0i.ctrl[j]);
            }
            for(j = 0; j <= 3; j++) {
                SK.GetEntity(ei1->point[j])->PointForceTo(bi1.ctrl[j]);
            }

            Constraint::ConstrainCoincident(e0i->point[3], ei1->point[0]);
            if(i == 0) hep0n = e0i->point[0];
            hep1n = ei1->point[3];
            hepin = e0i->point[3];
        } else {
            hRequest r = AddRequest(Request::CUBIC, false);
            Entity *e = SK.GetEntity(r.entity(0));

            for(j = 0; j <= 3; j++) {
                SK.GetEntity(e->point[j])->PointForceTo(sb->ctrl[j]);
            }

            if(i == 0) hep0n = e->point[0];
            hep1n = e->point[3];
        }
    }

    sbl.Clear();

    ReplacePointInConstraints(hep0, hep0n);
    ReplacePointInConstraints(hep1, hep1n);
    return hepin;
}

hEntity GraphicsWindow::SplitEntity(hEntity he, Vector pinter) {
    Entity *e = SK.GetEntity(he);
    int entityType = e->type;

    hEntity ret;
    if(e->IsCircle()) {
        ret = SplitCircle(he, pinter);
    } else if(e->type == Entity::LINE_SEGMENT) {
        ret = SplitLine(he, pinter);
    } else if(e->type == Entity::CUBIC || e->type == Entity::CUBIC_PERIODIC) {
        ret = SplitCubic(he, pinter);
    } else {
        Error("Couldn't split this entity; lines, circles, or cubics only.");
        return Entity::NO_ENTITY;
    }

    // Finally, delete the request that generated the original entity.
    int reqType = EntReqTable::GetRequestForEntity(entityType);
    SK.request.ClearTags();
    for(int i = 0; i < SK.request.n; i++) {
        Request *r = &(SK.request.elem[i]);
        if(r->group.v != activeGroup.v) continue;
        if(r->type != reqType) continue;
    
        // If the user wants to keep the old entities around, they can just
        // mark them construction first.
        if(he.v == r->h.entity(0).v && !r->construction) {
            r->tag = 1;
            break;
        }
    }
    DeleteTaggedRequests();

    return ret;
}

void GraphicsWindow::SplitLinesOrCurves(void) {
    if(!LockedInWorkplane()) {
        Error("Must be sketching in workplane to split.");
        return;
    }

    GroupSelection();
    if(!(gs.n == 2 &&(gs.lineSegments +
                      gs.circlesOrArcs + 
                      gs.cubics +
                      gs.periodicCubics) == 2))
    {
        Error("Select two entities that intersect each other (e.g. two lines "
              "or two circles or a circle and a line).");
        return;
    }

    hEntity ha = gs.entity[0],
            hb = gs.entity[1];
    Entity *ea = SK.GetEntity(ha),
           *eb = SK.GetEntity(hb);
   
    // Compute the possibly-rational Bezier curves for each of these entities
    SBezierList sbla, sblb;
    ZERO(&sbla);
    ZERO(&sblb);
    ea->GenerateBezierCurves(&sbla);
    eb->GenerateBezierCurves(&sblb);
    // and then compute the points where they intersect, based on those curves.
    SPointList inters;
    ZERO(&inters);
    sbla.AllIntersectionsWith(&sblb, &inters);

    // If there's multiple points, then just take the first one.
    if(inters.l.n > 0) {
        Vector pi = inters.l.elem[0].p;
        SS.UndoRemember();
        hEntity hia = SplitEntity(ha, pi),
                hib = SplitEntity(hb, pi);
        // SplitEntity adds the coincident constraints to join the split halves
        // of each original entity; and then we add the constraint to join
        // the two entities together at the split point.
        if(hia.v && hib.v) {
            Constraint::ConstrainCoincident(hia, hib);
        }
    } else {
        Error("Can't split; no intersection found.");
    }

    // All done, clean up and regenerate.
    inters.Clear();
    sbla.Clear();
    sblb.Clear();
    ClearSelection();
    SS.later.generateAll = true;
}

