//-----------------------------------------------------------------------------
// Implementation of the Constraint menu, to create new constraints in
// the sketch.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"

namespace SolveSpace {

std::string Constraint::DescriptionString() const {
    std::string s;
    switch(type) {
        case Type::POINTS_COINCIDENT:   s = C_("constr-name", "pts-coincident"); break;
        case Type::PT_PT_DISTANCE:      s = C_("constr-name", "pt-pt-distance"); break;
        case Type::PT_LINE_DISTANCE:    s = C_("constr-name", "pt-line-distance"); break;
        case Type::PT_PLANE_DISTANCE:   s = C_("constr-name", "pt-plane-distance"); break;
        case Type::PT_FACE_DISTANCE:    s = C_("constr-name", "pt-face-distance"); break;
        case Type::PROJ_PT_DISTANCE:    s = C_("constr-name", "proj-pt-pt-distance"); break;
        case Type::PT_IN_PLANE:         s = C_("constr-name", "pt-in-plane"); break;
        case Type::PT_ON_LINE:          s = C_("constr-name", "pt-on-line"); break;
        case Type::PT_ON_FACE:          s = C_("constr-name", "pt-on-face"); break;
        case Type::EQUAL_LENGTH_LINES:  s = C_("constr-name", "eq-length"); break;
        case Type::EQ_LEN_PT_LINE_D:    s = C_("constr-name", "eq-length-and-pt-ln-dist"); break;
        case Type::EQ_PT_LN_DISTANCES:  s = C_("constr-name", "eq-pt-line-distances"); break;
        case Type::LENGTH_RATIO:        s = C_("constr-name", "length-ratio"); break;
        case Type::ARC_ARC_LEN_RATIO:   s = C_("constr-name", "arc-arc-length-ratio"); break;
        case Type::ARC_LINE_LEN_RATIO:  s = C_("constr-name", "arc-line-length-ratio"); break;
        case Type::LENGTH_DIFFERENCE:   s = C_("constr-name", "length-difference"); break;
        case Type::ARC_ARC_DIFFERENCE:  s = C_("constr-name", "arc-arc-len-difference"); break;
        case Type::ARC_LINE_DIFFERENCE: s = C_("constr-name", "arc-line-len-difference"); break;
        case Type::SYMMETRIC:           s = C_("constr-name", "symmetric"); break;
        case Type::SYMMETRIC_HORIZ:     s = C_("constr-name", "symmetric-h"); break;
        case Type::SYMMETRIC_VERT:      s = C_("constr-name", "symmetric-v"); break;
        case Type::SYMMETRIC_LINE:      s = C_("constr-name", "symmetric-line"); break;
        case Type::AT_MIDPOINT:         s = C_("constr-name", "at-midpoint"); break;
        case Type::HORIZONTAL:          s = C_("constr-name", "horizontal"); break;
        case Type::VERTICAL:            s = C_("constr-name", "vertical"); break;
        case Type::DIAMETER:            s = C_("constr-name", "diameter"); break;
        case Type::PT_ON_CIRCLE:        s = C_("constr-name", "pt-on-circle"); break;
        case Type::SAME_ORIENTATION:    s = C_("constr-name", "same-orientation"); break;
        case Type::ANGLE:               s = C_("constr-name", "angle"); break;
        case Type::PARALLEL:            s = C_("constr-name", "parallel"); break;
        case Type::ARC_LINE_TANGENT:    s = C_("constr-name", "arc-line-tangent"); break;
        case Type::CUBIC_LINE_TANGENT:  s = C_("constr-name", "cubic-line-tangent"); break;
        case Type::CURVE_CURVE_TANGENT: s = C_("constr-name", "curve-curve-tangent"); break;
        case Type::PERPENDICULAR:       s = C_("constr-name", "perpendicular"); break;
        case Type::EQUAL_RADIUS:        s = C_("constr-name", "eq-radius"); break;
        case Type::EQUAL_ANGLE:         s = C_("constr-name", "eq-angle"); break;
        case Type::EQUAL_LINE_ARC_LEN:  s = C_("constr-name", "eq-line-len-arc-len"); break;
        case Type::WHERE_DRAGGED:       s = C_("constr-name", "lock-where-dragged"); break;
        case Type::COMMENT:             s = C_("constr-name", "comment"); break;
        default:                        s = "???"; break;
    }

    return ssprintf("c%03x-%s", h.v, s.c_str());
}

#ifndef LIBRARY

//-----------------------------------------------------------------------------
// Delete all constraints with the specified type, entityA, ptA. We use this
// when auto-removing constraints that would become redundant.
//-----------------------------------------------------------------------------
void Constraint::DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA)
{
    SK.constraint.ClearTags();
    for(auto &constraint : SK.constraint) {
        ConstraintBase *ct = &constraint;
        if(ct->type != type) continue;

        if(ct->entityA != entityA) continue;
        if(ct->ptA != ptA) continue;
        ct->tag = 1;
    }
    SK.constraint.RemoveTagged();
    // And no need to do anything special, since nothing
    // ever depends on a constraint. But do clear the
    // hover, in case the just-deleted constraint was
    // hovered.
    SS.GW.hover.Clear();
}

hConstraint Constraint::AddConstraint(Constraint *c, bool rememberForUndo) {
    if(rememberForUndo) SS.UndoRemember();

    hConstraint hc = SK.constraint.AddAndAssignId(c);
    SK.GetConstraint(hc)->Generate(&SK.param);

    SS.MarkGroupDirty(c->group);
    SK.GetGroup(c->group)->dofCheckOk = false;
    return c->h;
}

hConstraint Constraint::Constrain(Constraint::Type type, hEntity ptA, hEntity ptB,
                                  hEntity entityA, hEntity entityB,
                                  bool other, bool other2)
{
    Constraint c = {};
    c.group = SS.GW.activeGroup;
    c.workplane = SS.GW.ActiveWorkplane();
    c.type = type;
    c.ptA = ptA;
    c.ptB = ptB;
    c.entityA = entityA;
    c.entityB = entityB;
    c.other = other;
    c.other2 = other2;
    return AddConstraint(&c, /*rememberForUndo=*/false);
}

hConstraint Constraint::TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB,
                                     hEntity entityA, hEntity entityB,
                                     bool other, bool other2) {
    int rankBefore, rankAfter;
    SolveResult howBefore = SS.TestRankForGroup(SS.GW.activeGroup, &rankBefore);
    hConstraint hc = Constrain(type, ptA, ptB, entityA, entityB, other, other2);
    SolveResult howAfter = SS.TestRankForGroup(SS.GW.activeGroup, &rankAfter);
    // There are two cases where the constraint is clearly redundant:
    //   * If the group wasn't overconstrained and now it is;
    //   * If the group was overconstrained, and adding the constraint doesn't change rank at all.
    if((howBefore == SolveResult::OKAY && howAfter == SolveResult::REDUNDANT_OKAY) ||
       (howBefore == SolveResult::REDUNDANT_OKAY && howAfter == SolveResult::REDUNDANT_OKAY &&
            rankBefore == rankAfter)) {
        SK.constraint.RemoveById(hc);
        hc = {};
    }
    return hc;
}

hConstraint Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) {
    return Constrain(Type::POINTS_COINCIDENT, ptA, ptB,
        Entity::NO_ENTITY, Entity::NO_ENTITY, /*other=*/false, /*other2=*/false);
}

bool Constraint::ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc,
                                         Entity *arcendpoint) {
    Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
           l1 = SK.GetEntity(line->point[1])->PointGetNum();
    Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(),
           a2 = SK.GetEntity(arc->point[2])->PointGetNum();
    if(l0.Equals(a1) || l1.Equals(a1)) {
        c->other = false;
    } else if(l0.Equals(a2) || l1.Equals(a2)) {
        c->other = true;
    } else if(nullptr != arcendpoint) {
        Vector p = arcendpoint->PointGetNum();
        if(a1.Equals(p)) {
            c->other = false;
        } else if(a2.Equals(p)) {
            c->other = true;
        } else {
            Error(_("The point you selected does not belong to the arc. "
                    "The arc and line segment do not share an end point.\n\n"
                    "Select the end point of the arc at which you want "
                    "it to be tangent to the line."));
            return false;
        }
    } else {
        Error(_("The tangent arc and line segment must share an "
                "endpoint. Constrain them with Constrain -> "
                "On Point before constraining tangent.\n\n"
                "Alternatively select the end point of the arc "
                "at which you want it to be tangent to the line."));
        return false;
    }
    return true;
}

bool Constraint::ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic,
                                           Entity *curveendpoint) {
    Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
           l1 = SK.GetEntity(line->point[1])->PointGetNum();
    Vector as = cubic->CubicGetStartNum(),
           af = cubic->CubicGetFinishNum();

    if(l0.Equals(as) || l1.Equals(as)) {
        c->other = false;
    } else if(l0.Equals(af) || l1.Equals(af)) {
        c->other = true;
    } else if(nullptr != curveendpoint) {
        Vector p = curveendpoint->PointGetNum();
        if(as.Equals(p)) {
            c->other = false;
        } else if(af.Equals(p)) {
            c->other = true;
        } else {
            Error(_("The point you selected is not an end point of the cubic spline. "
                    "The spline and line segment do not share an end point.\n\n"
                    "Select the end point of the spline at which you want "
                    "it to be tangent to the line."));
            return false;
        }
    } else {
        Error(_("The tangent cubic spline and line segment must share an "
                "endpoint. Constrain them with Constrain -> "
                "On Point before constraining tangent.\n\n"
                "Alternatively select the end point of the cubic spline "
                "at which you want it to be tangent to the line."));
        return false;
    }
    return true;
}

bool Constraint::ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB, Entity *epA,
                                            Entity *epB) {
    Vector as = eA->EndpointStart(),
           af = eA->EndpointFinish(),
           bs = eB->EndpointStart(),
           bf = eB->EndpointFinish();
    if(as.Equals(bs)) {
        c->other = false;
        c->other2 = false;
    } else if(as.Equals(bf)) {
        c->other = false;
        c->other2 = true;
    } else if(af.Equals(bs)) {
        c->other = true;
        c->other2 = false;
    } else if(af.Equals(bf)) {
        c->other = true;
        c->other2 = true;
    } else if((nullptr != epA) && (nullptr != epB)) {
        Vector pa = epA->PointGetNum(),
               pb = epB->PointGetNum();
        if((as.Equals(pa) && bs.Equals(pb)) || (as.Equals(pb) && bs.Equals(pa))) {
            c->other  = false;
            c->other2 = false;
        } else if((as.Equals(pa) && bf.Equals(pb)) || (as.Equals(pb) && bf.Equals(pa))) {
            c->other  = false;
            c->other2 = true;
        } else if((af.Equals(pa) && bs.Equals(pb)) || (af.Equals(pb) && bs.Equals(pa))) {
            c->other  = true;
            c->other2 = false;
        } else if((af.Equals(pa) && bf.Equals(pb)) || (af.Equals(pb) && bf.Equals(pa))) {
            c->other  = true;
            c->other2 = true;
        } else {
            Error(_("The points you selected are not end points of the two curves. "
                    "The curves do not share an end point.\n\n"
                    "Select the end points of both curves at which you want "
                    "them to be tangent to each other."));
            return false;
        }
    } else {
        Error(_("The curves must share an endpoint. Constrain them "
                "with Constrain -> On Point before constraining "
                "tangent.\n\n"
                "Alternatively select the end points of both "
                "curves at which you want the curves to be tangent."));
                return false;
    }
    return true;
}

void Constraint::MenuConstrain(Command id) {
    std::vector<Constraint> newcons;

    Constraint c = {};
    c.group = SS.GW.activeGroup;
    c.workplane = SS.GW.ActiveWorkplane();

    SS.GW.GroupSelection();
    auto const &gs = SS.GW.gs;

    switch(id) {
        case Command::DISTANCE_DIA:
        case Command::REF_DISTANCE: {
            if(gs.points == 2 && gs.n == 2) {
                c.type = Type::PT_PT_DISTANCE;
                c.ptA = gs.point[0];
                c.ptB = gs.point[1];
            } else if(gs.lineSegments == 1 && gs.n == 1) {
                c.type = Type::PT_PT_DISTANCE;
                Entity *e = SK.GetEntity(gs.entity[0]);
                c.ptA = e->point[0];
                c.ptB = e->point[1];
            } else if(gs.vectors == 1 && gs.points == 2 && gs.n == 3) {
                c.type = Type::PROJ_PT_DISTANCE;
                c.ptA = gs.point[0];
                c.ptB = gs.point[1];
                c.entityA = gs.vector[0];
            } else if(gs.workplanes == 1 && gs.points == 1 && gs.n == 2) {
                c.type = Type::PT_PLANE_DISTANCE;
                c.ptA = gs.point[0];
                c.entityA = gs.entity[0];
            } else if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) {
                c.type = Type::PT_LINE_DISTANCE;
                c.ptA = gs.point[0];
                c.entityA = gs.entity[0];
            } else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) {
                c.type = Type::PT_FACE_DISTANCE;
                c.ptA = gs.point[0];
                c.entityA = gs.face[0];
            } else if(gs.circlesOrArcs == 1 && gs.n == 1) {
                c.type = Type::DIAMETER;
                c.entityA = gs.entity[0];
                Entity* arc = SK.GetEntity(gs.entity[0]);
                if ((arc->type == EntityBase::Type::ARC_OF_CIRCLE)
                    && (!SS.arcDimDefaultDiameter))
                {
                  c.other = true;
                }
            } else {
                Error(_("Bad selection for distance / diameter constraint. This "
                        "constraint can apply to:\n\n"
                        "    * two points (distance between points)\n"
                        "    * a line segment (length)\n"
                        "    * two points and a line segment or normal (projected distance)\n"
                        "    * a workplane and a point (minimum distance)\n"
                        "    * a line segment and a point (minimum distance)\n"
                        "    * a plane face and a point (minimum distance)\n"
                        "    * a circle or an arc (diameter)\n"));
                return;
            }
            if(c.type == Type::PT_PT_DISTANCE || c.type == Type::PROJ_PT_DISTANCE) {
                Vector n = SS.GW.projRight.Cross(SS.GW.projUp);
                Vector a = SK.GetEntity(c.ptA)->PointGetNum();
                Vector b = SK.GetEntity(c.ptB)->PointGetNum();
                c.disp.offset = n.Cross(a.Minus(b));
                c.disp.offset = (c.disp.offset).WithMagnitude(50/SS.GW.scale);
            } else {
                c.disp.offset = Vector::From(0, 0, 0);
            }

            if(id == Command::REF_DISTANCE) {
                c.reference = true;
            }

            c.valA = 0;
            c.ModifyToSatisfy();
            AddConstraint(&c);
            newcons.push_back(c);
            break;
        }

        case Command::ON_ENTITY:
            if(gs.points >= 2 && gs.points == gs.n) {
                c.type = Type::POINTS_COINCIDENT;
                c.ptA = gs.point[0];
                for(int k = 1; k < gs.points; k++) {
                    c.ptB = gs.point[k];
                    newcons.push_back(c);
                }
            } else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) {
                c.type = Type::PT_IN_PLANE;
                c.ptA = gs.point[0];
                c.entityA = gs.entity[0];
                newcons.push_back(c);
            } else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) {
                c.type = Type::PT_ON_LINE;
                c.ptA = gs.point[0];
                c.entityA = gs.entity[0];
                newcons.push_back(c);
            } else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) {
                c.type = Type::PT_ON_CIRCLE;
                c.ptA = gs.point[0];
                c.entityA = gs.entity[0];
                newcons.push_back(c);
            } else if(gs.points == 1 && gs.faces >= 1 && gs.n == gs.points+gs.faces) {
                c.type = Type::PT_ON_FACE;
                c.ptA = gs.point[0];
                for (int k=0; k<gs.faces; k++) {
                    c.entityA = gs.face[k];
                    newcons.push_back(c);
                }
            } else {
                Error(_("Bad selection for on point / curve / plane constraint. "
                        "This constraint can apply to:\n\n"
                        "    * two or more points (points coincident)\n"
                        "    * a point and a workplane (point in plane)\n"
                        "    * a point and a line segment (point on line)\n"
                        "    * a point and a circle or arc (point on curve)\n"
                        "    * a point and one to three plane faces (point on face(s))\n"));
                return;
            }
            for (auto&& nc : newcons)
              AddConstraint(&nc);
            break;

        case Command::EQUAL:
            if(gs.lineSegments >= 2 && gs.lineSegments == gs.n) {
                c.type = Type::EQUAL_LENGTH_LINES;
                c.entityA = gs.entity[0];
                for (std::vector<hEntity>::size_type k = 1;k < gs.entity.size(); ++k){
                    c.entityB = gs.entity[k];
                    newcons.push_back(c);
                }
            } else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) {
                c.type = Type::EQ_PT_LN_DISTANCES;
                c.entityA = gs.entity[0];
                c.ptA = gs.point[0];
                c.entityB = gs.entity[1];
                c.ptB = gs.point[1];
                newcons.push_back(c);
            } else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) {
                // The same line segment for the distances, but different
                // points.
                c.type = Type::EQ_PT_LN_DISTANCES;
                c.entityA = gs.entity[0];
                c.ptA = gs.point[0];
                c.entityB = gs.entity[0];
                c.ptB = gs.point[1];
                newcons.push_back(c);
            } else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) {
                c.type = Type::EQ_LEN_PT_LINE_D;
                c.entityA = gs.entity[0];
                c.entityB = gs.entity[1];
                c.ptA = gs.point[0];
                newcons.push_back(c);
            } else  if(gs.circlesOrArcs >= 2 && gs.circlesOrArcs == gs.n) {
                c.type = Type::EQUAL_RADIUS;
                c.entityA = gs.entity[0];
                for (std::vector<hEntity>::size_type k = 1;k < gs.entity.size(); ++k){
                    c.entityB = gs.entity[k];
                    newcons.push_back(c);
                }
            } else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) {
                c.type = Type::EQUAL_LINE_ARC_LEN;
                if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
                    c.entityA = gs.entity[1];
                    c.entityB = gs.entity[0];
                } else {
                    c.entityA = gs.entity[0];
                    c.entityB = gs.entity[1];
                }
                newcons.push_back(c);
            } else {
                Error(_("Bad selection for equal length / radius constraint. "
                        "This constraint can apply to:\n\n"
                        "    * two or more line segments (equal length)\n"
                        "    * two line segments and two points "
                                "(equal point-line distances)\n"
                        "    * a line segment and two points "
                                "(equal point-line distances)\n"
                        "    * a line segment, and a point and line segment "
                                "(point-line distance equals length)\n"
                        "    * two or more circles or arcs (equal radius)\n"
                        "    * a line segment and an arc "
                                "(line segment length equals arc length)\n"));
                return;
            }
            SS.UndoRemember();
            for (auto&& nc : newcons){
                if(nc.type == Type::EQUAL_ANGLE) {
                    // Infer the nearest supplementary angle from the sketch.
                    Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(),
                            b1 = SK.GetEntity(c.entityB)->VectorGetNum(),
                            a2 = SK.GetEntity(c.entityC)->VectorGetNum(),
                            b2 = SK.GetEntity(c.entityD)->VectorGetNum();
                    double d1 = a1.Dot(b1), d2 = a2.Dot(b2);

                    if(d1*d2 < 0) {
                        nc.other = true;
                    }
                }
                AddConstraint(&nc, /*rememberForUndo=*/false);
            }
            break;

        case Command::RATIO:
            if(gs.lineSegments == 2 && gs.n == 2) {
                c.type = Type::LENGTH_RATIO;
                c.entityA = gs.entity[0];
                c.entityB = gs.entity[1];
            }
            else if(gs.arcs == 2 && gs.n == 2) {
                c.type = Type::ARC_ARC_LEN_RATIO;
                c.entityA = gs.entity[0];
                c.entityB = gs.entity[1];
            }
            else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
                c.type = Type::ARC_LINE_LEN_RATIO;
                if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
                    c.entityA = gs.entity[1];
                    c.entityB = gs.entity[0];
                } else {
                    c.entityA = gs.entity[0];
                    c.entityB = gs.entity[1];
                }
            } else {
                Error(_("Bad selection for length ratio constraint. This "
                        "constraint can apply to:\n\n"
                        "    * two line segments\n"
                        "    * two arcs\n"
                        "    * one arc and one line segment\n"));
                return;
            }

            c.valA = 0;
            c.ModifyToSatisfy();
            AddConstraint(&c);
            newcons.push_back(c);
            break;

        case Command::DIFFERENCE:
            if(gs.lineSegments == 2 && gs.n == 2) {
                c.type = Type::LENGTH_DIFFERENCE;
                c.entityA = gs.entity[0];
                c.entityB = gs.entity[1];
            }
            else if(gs.arcs == 2 && gs.n == 2) {
                c.type = Type::ARC_ARC_DIFFERENCE;
                c.entityA = gs.entity[0];
                c.entityB = gs.entity[1];
            }
            else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
                c.type = Type::ARC_LINE_DIFFERENCE;
                if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
                    c.entityA = gs.entity[1];
                    c.entityB = gs.entity[0];
                } else {
                    c.entityA = gs.entity[0];
                    c.entityB = gs.entity[1];
                }
            } else {
                Error(_("Bad selection for length difference constraint. This "
                        "constraint can apply to:\n\n"
                        "    * two line segments\n"
                        "    * two arcs\n"
                        "    * one arc and one line segment\n"));
                return;
            }

            c.valA = 0;
            c.ModifyToSatisfy();
            AddConstraint(&c);
            newcons.push_back(c);
            break;

        case Command::AT_MIDPOINT:
            if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) {
                c.type = Type::AT_MIDPOINT;
                c.entityA = gs.entity[0];
                c.ptA = gs.point[0];

                // If a point is at-midpoint, then no reason to also constrain
                // it on-line; so auto-remove that.  Handle as one undo group.
                SS.UndoRemember();
                DeleteAllConstraintsFor(Type::PT_ON_LINE, c.entityA, c.ptA);
                AddConstraint(&c, /*rememberForUndo=*/false);
                newcons.push_back(c);
                break;
            } else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) {
                c.type = Type::AT_MIDPOINT;
                int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0;
                c.entityA = gs.entity[i];
                c.entityB = gs.entity[1-i];
                AddConstraint(&c);
                newcons.push_back(c);
            } else {
                Error(_("Bad selection for at midpoint constraint. This "
                        "constraint can apply to:\n\n"
                        "    * a line segment and a point "
                              "(point at midpoint)\n"
                        "    * a line segment and a workplane "
                              "(line's midpoint on plane)\n"));
                return;
            }

            break;

        case Command::SYMMETRIC:
            if(gs.points == 2 &&
                                ((gs.workplanes == 1 && gs.n == 3) ||
                                 (gs.n == 2)))
            {
                if(gs.entities > 0)
                    c.entityA = gs.entity[0];
                c.ptA = gs.point[0];
                c.ptB = gs.point[1];
                c.type = Type::SYMMETRIC;
            } else if(gs.lineSegments == 1 &&
                                ((gs.workplanes == 1 && gs.n == 2) ||
                                 (gs.n == 1)))
            {
                Entity *line;
                if(SK.GetEntity(gs.entity[0])->IsWorkplane()) {
                    line = SK.GetEntity(gs.entity[1]);
                    c.entityA = gs.entity[0];
                } else {
                    line = SK.GetEntity(gs.entity[0]);
                }
                c.ptA = line->point[0];
                c.ptB = line->point[1];
                c.type = Type::SYMMETRIC;
            } else if(SS.GW.LockedInWorkplane()
                        && gs.lineSegments == 2 && gs.n == 2)
            {
                Entity *l0 = SK.GetEntity(gs.entity[0]),
                       *l1 = SK.GetEntity(gs.entity[1]);

                if((l1->group != SS.GW.activeGroup) ||
                   (l1->construction && !(l0->construction)))
                {
                    swap(l0, l1);
                }
                c.ptA = l1->point[0];
                c.ptB = l1->point[1];
                c.entityA = l0->h;
                c.type = Type::SYMMETRIC_LINE;
            } else if(SS.GW.LockedInWorkplane()
                        && gs.lineSegments == 1 && gs.points == 2 && gs.n == 3)
            {
                c.ptA = gs.point[0];
                c.ptB = gs.point[1];
                c.entityA = gs.entity[0];
                c.type = Type::SYMMETRIC_LINE;
            } else {
                Error(_("Bad selection for symmetric constraint. This constraint "
                        "can apply to:\n\n"
                        "    * two points or a line segment "
                            "(symmetric about workplane's coordinate axis)\n"
                        "    * line segment, and two points or a line segment "
                            "(symmetric about line segment)\n"
                        "    * workplane, and two points or a line segment "
                            "(symmetric about workplane)\n"));
                return;
            }
            // We may remove constraints so remember manually
            if(c.entityA == Entity::NO_ENTITY) {
                // Horizontal / vertical symmetry, implicit symmetry plane
                // normal to the workplane
                if(c.workplane == Entity::FREE_IN_3D) {
                    Error(_("A workplane must be active when constraining "
                            "symmetric without an explicit symmetry plane."));
                    return;
                }
                Vector pa = SK.GetEntity(c.ptA)->PointGetNum();
                Vector pb = SK.GetEntity(c.ptB)->PointGetNum();
                Vector dp = pa.Minus(pb);
                EntityBase *norm = SK.GetEntity(c.workplane)->Normal();;
                Vector u = norm->NormalU(), v = norm->NormalV();
                if(fabs(dp.Dot(u)) > fabs(dp.Dot(v))) {
                    c.type = Type::SYMMETRIC_HORIZ;
                } else {
                    c.type = Type::SYMMETRIC_VERT;
                }
                if(gs.lineSegments == 1) {
                    // If this line segment is already constrained horiz or
                    // vert, then auto-remove that redundant constraint.
                    // Handle as one undo group.
                    SS.UndoRemember();
                    DeleteAllConstraintsFor(Type::HORIZONTAL, (gs.entity[0]),
                        Entity::NO_ENTITY);
                    DeleteAllConstraintsFor(Type::VERTICAL, (gs.entity[0]),
                        Entity::NO_ENTITY);
                    AddConstraint(&c, /*rememberForUndo=*/false);
                    newcons.push_back(c);
                    break;
                }
            }
            AddConstraint(&c);
            newcons.push_back(c);
            break;

        case Command::VERTICAL:
        case Command::HORIZONTAL: {
            if(id == Command::HORIZONTAL) {
                c.type = Type::HORIZONTAL;
            } else {
                c.type = Type::VERTICAL;
            }
            if(c.workplane == Entity::FREE_IN_3D) {
                Error(_("Activate a workplane (with Sketch -> In Workplane) before "
                        "applying a horizontal or vertical constraint."));
                return;
            }
            if(gs.lineSegments > 0 && gs.lineSegments == gs.n) {
                for (auto enti : gs.entity){
                    c.entityA = enti;
                    newcons.push_back(c);
                }
            } else if(gs.points >= 2 && gs.n == gs.points) {
                c.ptA = gs.point[0];
                for (int k = 1; k<gs.points; k++) {
                  c.ptB = gs.point[k];
                  newcons.push_back(c);
                }
            } else {
                Error(_("Bad selection for horizontal / vertical constraint. "
                        "This constraint can apply to:\n\n"
                        "    * two or more points\n"
                        "    * one or more line segments\n"));
                return;
            }
            SS.UndoRemember();
            for (auto && nc: newcons)
                AddConstraint(&nc, /*rememberForUndo=*/false);
            break;
        }

        case Command::ORIENTED_SAME: {
            if(gs.anyNormals == 2 && gs.n == 2) {
                c.type = Type::SAME_ORIENTATION;
                c.entityA = gs.anyNormal[0];
                c.entityB = gs.anyNormal[1];
            } else {
                Error(_("Bad selection for same orientation constraint. This "
                        "constraint can apply to:\n\n"
                        "    * two normals\n"));
                return;
            }
            SS.UndoRemember();

            Entity *nfree = SK.GetEntity(c.entityA);
            Entity *nref  = SK.GetEntity(c.entityB);
            if(nref->group == SS.GW.activeGroup) {
                swap(nref, nfree);
            }
            if(nfree->group == SS.GW.activeGroup && nref->group != SS.GW.activeGroup) {
                // nfree is free, and nref is locked (since it came from a
                // previous group); so let's force nfree aligned to nref,
                // and make convergence easy
                Vector ru = nref ->NormalU(), rv = nref ->NormalV();
                Vector fu = nfree->NormalU(), fv = nfree->NormalV();

                if(fabs(fu.Dot(ru)) < fabs(fu.Dot(rv))) {
                    // There might be an odd*90 degree rotation about the
                    // normal vector; allow that, since the numerical
                    // constraint does
                    swap(ru, rv);
                }
                fu = fu.Dot(ru) > 0 ? ru : ru.ScaledBy(-1);
                fv = fv.Dot(rv) > 0 ? rv : rv.ScaledBy(-1);

                nfree->NormalForceTo(Quaternion::From(fu, fv));
            }
            AddConstraint(&c, /*rememberForUndo=*/false);
            newcons.push_back(c);
            break;
        }

        case Command::OTHER_ANGLE:
            if(gs.constraints == 1 && gs.n == 0) {
                Constraint *c = SK.GetConstraint(gs.constraint[0]);
                if(c->type == Type::ANGLE) {
                    SS.UndoRemember();
                    c->other = !(c->other);
                    c->ModifyToSatisfy();
                    break;
                }
                if(c->type == Type::EQUAL_ANGLE) {
                    SS.UndoRemember();
                    c->other = !(c->other);
                    SS.MarkGroupDirty(c->group);
                    break;
                }
            }
            Error(_("Must select an angle constraint."));
            return;

        case Command::REFERENCE:
            if(gs.constraints == 1 && gs.n == 0) {
                Constraint *c = SK.GetConstraint(gs.constraint[0]);
                if(c->HasLabel() && c->type != Type::COMMENT) {
                    SS.UndoRemember();
                    (c->reference) = !(c->reference);
                    SS.MarkGroupDirty(c->group, /*onlyThis=*/true);
                    break;
                }
            }
            Error(_("Must select a constraint with associated label."));
            return;

        case Command::ANGLE:
        case Command::REF_ANGLE: {
            if(gs.vectors == 3 && gs.n == 3) {
                c.type = Type::EQUAL_ANGLE;
                c.entityA = gs.vector[0];
                c.entityB = gs.vector[1];
                c.entityC = gs.vector[1];
                c.entityD = gs.vector[2];
            } else if(gs.vectors == 4 && gs.n == 4) {
                c.type = Type::EQUAL_ANGLE;
                c.entityA = gs.vector[0];
                c.entityB = gs.vector[1];
                c.entityC = gs.vector[2];
                c.entityD = gs.vector[3];
            } else if(gs.vectors == 2 && gs.n == 2) {
                c.type = Type::ANGLE;
                c.entityA = gs.vector[0];
                c.entityB = gs.vector[1];
                c.valA = 0;
            } else {
                Error(_("Bad selection for angle constraint. This constraint "
                        "can apply to:\n\n"
                        "Angle between:\n"
                        "    * two line segments\n"
                        "    * a line segment and a normal\n"
                        "    * two normals\n"
                        "\nEqual angles:\n"
                        "    * four line segments or normals "
                        "(equal angle between A,B and C,D)\n"
                        "    * three line segments or normals "
                        "(equal angle between A,B and B,C)\n"));
                return;
            }

            Entity *ea = SK.GetEntity(c.entityA),
                   *eb = SK.GetEntity(c.entityB);
            if(ea->type == Entity::Type::LINE_SEGMENT &&
               eb->type == Entity::Type::LINE_SEGMENT)
            {
                Vector a0 = SK.GetEntity(ea->point[0])->PointGetNum(),
                       a1 = SK.GetEntity(ea->point[1])->PointGetNum(),
                       b0 = SK.GetEntity(eb->point[0])->PointGetNum(),
                       b1 = SK.GetEntity(eb->point[1])->PointGetNum();
                if(a0.Equals(b0) || a1.Equals(b1)) {
                    // okay, vectors should be drawn in same sense
                } else if(a0.Equals(b1) || a1.Equals(b0)) {
                    // vectors are in opposite sense
                    c.other = true;
                } else {
                    // no shared point; not clear which intersection to draw
                }
            }

            if(id == Command::REF_ANGLE) {
                c.reference = true;
            }

            c.ModifyToSatisfy();
            AddConstraint(&c);
            newcons.push_back(c);
            break;
        }

        case Command::PARALLEL:
            if(gs.faces == 2 && gs.n == 2) {
                c.type = Type::PARALLEL;
                c.entityA = gs.face[0];
                c.entityB = gs.face[1];            
                newcons.push_back(c);
            } else if(gs.vectors > 1 && gs.vectors == gs.n) {
                c.type = Type::PARALLEL;
                c.entityA = gs.vector[0];
                for (std::vector<hEntity>::size_type k = 1; k < gs.vector.size();++k ){
                    c.entityB = gs.vector[k];
                    newcons.push_back(c);
                }
            } else if(gs.lineSegments == 1 && gs.arcs == 1 &&
                      (gs.n == 2 || (gs.points == 1 && gs.n == 3))) {
                Entity *line = SK.GetEntity(gs.entity[0]),
                       *arc  = SK.GetEntity(gs.entity[1]);
                if(line->type == Entity::Type::ARC_OF_CIRCLE) {
                    swap(line, arc);
                }
                if(!ConstrainArcLineTangent(
                       &c, line, arc, (1 == gs.points) ? SK.GetEntity(gs.point[0]) : nullptr)) {
                    return;
                }
                c.type = Type::ARC_LINE_TANGENT;
                c.entityA = arc->h;
                c.entityB = line->h;
                newcons.push_back(c);
            } else if(gs.lineSegments == 1 && gs.cubics == 1 &&
                      (gs.n == 2 || (gs.points == 1 && gs.n == 3))) {
                Entity *line  = SK.GetEntity(gs.entity[0]),
                       *cubic = SK.GetEntity(gs.entity[1]);
                if(line->type == Entity::Type::CUBIC) {
                    swap(line, cubic);
                }
                if(!ConstrainCubicLineTangent(
                       &c, line, cubic, (1 == gs.points) ? SK.GetEntity(gs.point[0]) : nullptr)) {
                    return;
                }
                c.type = Type::CUBIC_LINE_TANGENT;
                c.entityA = cubic->h;
                c.entityB = line->h;
                newcons.push_back(c);
            } else if(gs.cubics + gs.arcs == 2 && (gs.n == 2 || (gs.points == 2 && gs.n == 4))) {
                if(!SS.GW.LockedInWorkplane()) {
                    Error(_("Curve-curve tangency must apply in workplane."));
                    return;
                }
                Entity *eA = SK.GetEntity(gs.entity[0]),
                       *eB = SK.GetEntity(gs.entity[1]);
                if(!ConstrainCurveCurveTangent(
                       &c, eA, eB, (2 == gs.points) ? SK.GetEntity(gs.point[0]) : nullptr,
                       (2 == gs.points) ? SK.GetEntity(gs.point[1]) : nullptr)) {
                    return;
                }
                c.type = Type::CURVE_CURVE_TANGENT;
                c.entityA = eA->h;
                c.entityB = eB->h;
                newcons.push_back(c);
            } else {
                Error(_("Bad selection for parallel / tangent constraint. This "
                        "constraint can apply to:\n\n"
                        "    * two faces\n"
                        "    * two or more line segments (parallel)\n"
                        "    * one or more line segments and one or more normals (parallel)\n"
                        "    * two or more normals (parallel)\n"
                        "    * two line segments, arcs, or beziers, that share "
                              "an endpoint (tangent)\n"
                        "    * two line segments, arcs, or beziers, that do not share "
                              "an endpoint and the end point(s) of the curve(s) (tangent)\n"));
                        return;
            }
            SS.UndoRemember();
            for (auto&& nc:newcons)
                AddConstraint(&nc, /*rememberForUndo=*/false);
            break;

        case Command::PERPENDICULAR:
            if(gs.faces == 2 && gs.n == 2) {
                c.type = Type::PERPENDICULAR;
                c.entityA = gs.face[0];
                c.entityB = gs.face[1];            
            } else if(gs.vectors == 2 && gs.n == 2) {
                c.type = Type::PERPENDICULAR;
                c.entityA = gs.vector[0];
                c.entityB = gs.vector[1];
            } else {
                Error(_("Bad selection for perpendicular constraint. This "
                        "constraint can apply to:\n\n"
                        "    * two faces\n"
                        "    * two line segments\n"
                        "    * a line segment and a normal\n"
                        "    * two normals\n"));
                return;
            }
            AddConstraint(&c);
            newcons.push_back(c);
            break;

        case Command::WHERE_DRAGGED:
            if(gs.points == 1 && gs.n == 1) {
                c.type = Type::WHERE_DRAGGED;
                c.ptA = gs.point[0];
            } else {
                Error(_("Bad selection for lock point where dragged constraint. "
                        "This constraint can apply to:\n\n"
                        "    * a point\n"));
                return;
            }
            AddConstraint(&c);
            newcons.push_back(c);
            break;

        case Command::COMMENT:
            if(gs.points == 1 && gs.n == 1) {
                c.type = Type::COMMENT;
                c.ptA = gs.point[0];
                c.group       = SS.GW.activeGroup;
                c.workplane   = SS.GW.ActiveWorkplane();
                c.comment     = _("NEW COMMENT -- DOUBLE-CLICK TO EDIT");
                AddConstraint(&c);
                newcons.push_back(c);
            } else {
                SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND;
                SS.GW.pending.command = Command::COMMENT;
                SS.GW.pending.description = _("click center of comment text");
                SS.ScheduleShowTW();
            }
            break;

        default: ssassert(false, "Unexpected menu ID");
    }
    for (auto nc:newcons){
        for(const Constraint &cc : SK.constraint) {
            if(nc.h != cc.h && nc.Equals(cc)) {
                // Oops, we already have this exact constraint. Remove the one we just added.
                SK.constraint.RemoveById(nc.h);
                SS.GW.ClearSelection();
                // And now select the old one, to give feedback.
                SS.GW.MakeSelected(cc.h);
                return;
            }
        }

        if(SK.constraint.FindByIdNoOops(nc.h)) {
            Constraint *constraint = SK.GetConstraint(nc.h);
            if(SS.TestRankForGroup(nc.group) == SolveResult::REDUNDANT_OKAY &&
                    !SK.GetGroup(SS.GW.activeGroup)->allowRedundant &&
                    constraint->HasLabel()) {
                constraint->reference = true;
            }
        }

        if((id == Command::DISTANCE_DIA || id == Command::ANGLE ||
            id == Command::RATIO || id == Command::DIFFERENCE) &&
                SS.immediatelyEditDimension) {
            SS.GW.EditConstraint(nc.h);
        }
    }

    SS.GW.ClearSelection();
}

#endif /* ! LIBRARY */

} // namespace SolveSpace
