import {Ref} from './shapes/ref';
import Vector from 'math/vector';
import {distanceAB} from "math/distance";

class AbstractConstraint {
  
}

export const Constraints = {
  Factory: {}
};

// ------------------------------------------------------------------------------------------------------------------ //

export class Coincident extends AbstractConstraint {

  reducible = true;

  static deserialize(refs, data) {
    return new Coincident(refs(data[0]), refs(data[1]));
  }

  constructor(a, b) {
    super();
    this.a = a;
    this.b = b;
    a.linked.push(b);
    b.linked.push(a);
  }


  getSolveData() {
    return [
      ['equal', [this.a._x, this.b._x], []],
      ['equal', [this.a._y, this.b._y], []]
    ];
  }

  serialize() {
    return [this.NAME, [this.a.id, this.b.id]];
  }

  getObjects() {
    return [this.a, this.b];
  }
}



Constraints.Coincident = Coincident;
Constraints.Coincident.prototype.NAME = 'coi';
Constraints.Coincident.prototype.UI_NAME = 'Coincident';

Constraints.Factory[Coincident.prototype.NAME] = Coincident.deserialize;

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.RadiusOffset = function(arc1, arc2, offset) {
  this.arc1 = arc1;
  this.arc2 = arc2;
  this.offset = offset;
};

Constraints.RadiusOffset.prototype.NAME = 'RadiusOffset';
Constraints.RadiusOffset.prototype.UI_NAME = 'Radius Offset';

Constraints.RadiusOffset.prototype.getSolveData = function(resolver) {
  return [
    ['Diff', [this.arc1.r, this.arc2.r], [resolver(this.offset)]]
  ];
};

Constraints.RadiusOffset.prototype.serialize = function() {
  return [this.NAME, [this.arc1.id, this.arc2.id, this.offset]];
};

Constraints.Factory[Constraints.RadiusOffset.prototype.NAME] = function(refs, data) {
  return new Constraints.RadiusOffset(refs(data[0]), refs(data[1]), data[2]);
};

Constraints.RadiusOffset.prototype.getObjects = function() {
  return [this.arc1, this.arc2];
};

Constraints.RadiusOffset.prototype.SettableFields = {'offset' : "Enter the offset"};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.Lock = function(p, c) {
  this.p = p;
  this.c = c;
};

Constraints.Lock.prototype.NAME = 'lock';
Constraints.Lock.prototype.UI_NAME = 'Lock';

Constraints.Lock.prototype.getSolveData = function() {
  return [
    ['equalsTo', [this.p._x], [this.c.x]],
    ['equalsTo', [this.p._y], [this.c.y]]
  ];
};

Constraints.Lock.prototype.serialize = function() {
  return [this.NAME, [this.p.id, this.c]];
};

Constraints.Factory[Constraints.Lock.prototype.NAME] = function(refs, data) {
  return new Constraints.Lock(refs(data[0]), data[1]);
};


Constraints.Lock.prototype.getObjects = function() {
  return [this.p];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.Parallel = function(l1, l2) {
  this.l1 = l1;
  this.l2 = l2;
};

Constraints.Parallel.prototype.NAME = 'parallel';
Constraints.Parallel.prototype.UI_NAME = 'Parallel';

Constraints.Parallel.prototype.getSolveData = function() {
  const params = [];
  this.l1.collectParams(params);
  this.l2.collectParams(params);
  return [[this.NAME, params, []]];
};

Constraints.Parallel.prototype.serialize = function() {
  return [this.NAME, [this.l1.id, this.l2.id]];
};

Constraints.Factory[Constraints.Parallel.prototype.NAME] = function(refs, data) {
  return new Constraints.Parallel(refs(data[0]), refs(data[1]));
};

Constraints.Parallel.prototype.getObjects = function() {
  return [this.l1, this.l2];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.Perpendicular = function(l1, l2) {
  this.l1 = l1;
  this.l2 = l2;
};

Constraints.Perpendicular.prototype.NAME = 'perpendicular';
Constraints.Perpendicular.prototype.UI_NAME = 'Perpendicular';

Constraints.Perpendicular.prototype.getSolveData = function() {
  const params = [];
  this.l1.collectParams(params);
  this.l2.collectParams(params);
  return [[this.NAME, params, []]];
};

Constraints.Perpendicular.prototype.serialize = function() {
  return [this.NAME, [this.l1.id, this.l2.id]];
};

Constraints.Factory[Constraints.Perpendicular.prototype.NAME] = function(refs, data) {
  return new Constraints.Perpendicular(refs(data[0]), refs(data[1]));
};

Constraints.Perpendicular.prototype.getObjects = function() {
  return [this.l1, this.l2];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.P2LDistanceSigned = function(p, a, b, d) {
  this.p = p;
  this.a = a;
  this.b = b;
  this.d = d;
};

Constraints.P2LDistanceSigned.prototype.NAME = 'P2LDistanceSigned';
Constraints.P2LDistanceSigned.prototype.UI_NAME = 'Distance P & L';

Constraints.P2LDistanceSigned.prototype.getSolveData = function(resolver) {
  const params = [];
  this.p.collectParams(params);
  this.a.collectParams(params);
  this.b.collectParams(params);
  return [[this.NAME, params, [resolver(this.d)]]];
};

Constraints.P2LDistanceSigned.prototype.serialize = function() {
  return [this.NAME, [this.p.id, this.a.id, this.b.id, this.d]];
};

Constraints.Factory[Constraints.P2LDistanceSigned.prototype.NAME] = function(refs, data) {
  return new Constraints.P2LDistanceSigned(refs(data[0]), refs(data[1]), refs(data[2]), data[3]);
};

Constraints.P2LDistanceSigned.prototype.getObjects = function() {
  const collector = new Constraints.ParentsCollector();
  collector.check(this.a);
  collector.check(this.b);
  collector.parents.push(this.p);
  return collector.parents;
};

Constraints.P2LDistanceSigned.prototype.SettableFields = {'d' : "Enter the distance"};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.P2LDistance = function(p, l, d) {
  this.p = p;
  this.l = l;
  this.d = d;
};

Constraints.P2LDistance.prototype.NAME = 'P2LDistance';
Constraints.P2LDistance.prototype.UI_NAME = 'Distance P & L';

Constraints.P2LDistance.prototype.getSolveData = function(resolver) {
  const params = [];
  this.p.collectParams(params);
  this.l.collectParams(params);
  return [[this.NAME, params, [resolver(this.d)]]];
};

Constraints.P2LDistance.prototype.serialize = function() {
  return [this.NAME, [this.p.id, this.l.id, this.d]];
};

Constraints.Factory[Constraints.P2LDistance.prototype.NAME] = function(refs, data) {
  return new Constraints.P2LDistance(refs(data[0]), refs(data[1]), data[2]);
};

Constraints.P2LDistance.prototype.getObjects = function() {
  return [this.p, this.l];
};

Constraints.P2LDistance.prototype.SettableFields = {'d' : "Enter the distance"};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.MinLength = function(a, b, min) {
  this.a = a;
  this.b = b;
  this.min = min;
};

Constraints.MinLength.prototype.aux = true;
Constraints.MinLength.prototype.NAME = 'MinLength';
Constraints.MinLength.prototype.UI_NAME = 'MinLength';

Constraints.MinLength.prototype.getSolveData = function() {
  const params = [];
  this.a.collectParams(params);
  this.b.collectParams(params);
  return [[this.NAME, params, [this.min]]];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.P2LDistanceV = function(p, l, d) {
  this.p = p;
  this.l = l;
  this.d = d;
};

Constraints.P2LDistanceV.prototype.aux = true;
Constraints.P2LDistanceV.prototype.NAME = 'P2LDistanceV';
Constraints.P2LDistanceV.prototype.UI_NAME = 'Distance P & L';

Constraints.P2LDistanceV.prototype.getSolveData = function() {
  const params = [];
  this.p.collectParams(params);
  this.l.collectParams(params);
  params.push(this.d);
  return [[this.NAME, params]];
};

// We don't serialize auxiliary constraints
//
//Constraints.P2LDistanceV.prototype.serialize = function() {
//  return [this.NAME, [this.p.id, this.l.id, this.d.id]];
//};
//
//Constraints.Factory[Constraints.P2LDistanceV.prototype.NAME] = function(refs, data) {
//  return new Constraints.P2LDistanceV(refs(data[0]), refs(data[1]), refs(data[2]));
//};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.P2PDistance = function(p1, p2, d) {
  this.p1 = p1;
  this.p2 = p2;
  this.d = d;
};

Constraints.P2PDistance.prototype.NAME = 'P2PDistance';
Constraints.P2PDistance.prototype.UI_NAME = 'Distance Points';

Constraints.P2PDistance.prototype.getSolveData = function(resolver) {
  const params = [];
  this.p1.collectParams(params);
  this.p2.collectParams(params);
  return [[this.NAME, params, [resolver(this.d)]]];
};

Constraints.P2PDistance.prototype.serialize = function() {
  return [this.NAME, [this.p1.id, this.p2.id, this.d]];
};

Constraints.Factory[Constraints.P2PDistance.prototype.NAME] = function(refs, data) {
  return new Constraints.P2PDistance(refs(data[0]), refs(data[1]), data[2]);
};

Constraints.P2PDistance.prototype.getObjects = function() {
  return [this.p1, this.p2];
};

Constraints.P2PDistance.prototype.SettableFields = {'d' : "Enter the distance"};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.P2PDistanceV = function(p1, p2, d) {
  this.p1 = p1;
  this.p2 = p2;
  this.d = d;
};

Constraints.P2PDistanceV.prototype.aux = true;
Constraints.P2PDistanceV.prototype.NAME = 'P2PDistanceV';
Constraints.P2PDistanceV.prototype.UI_NAME = 'Distance Points';

Constraints.P2PDistanceV.prototype.getSolveData = function() {
  const params = [];
  this.p1.collectParams(params);
  this.p2.collectParams(params);
  params.push(this.d);
  return [[this.NAME, params]];
};

// We don't serialize auxiliary constraints
//
//Constraints.P2PDistanceV.prototype.serialize = function() {
//  return [this.NAME, [this.p1.id, this.p2.id, this.d.id]];
//};
//
//Constraints.Factory[Constraints.P2PDistanceV.prototype.NAME] = function(refs, data) {
//  return new Constraints.P2PDistanceV(refs(data[0]), refs(data[1]), refs(data[2]));
//};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.GreaterThan = function(p, limit) {
  this.p = p;
  this.limit = limit;
};

Constraints.GreaterThan.prototype.aux = true;
Constraints.GreaterThan.prototype.NAME = 'GreaterThan';
Constraints.GreaterThan.prototype.UI_NAME = 'Greater Than';

Constraints.GreaterThan.prototype.getSolveData = function() {
  return [[this.NAME, [this.p], [this.limit]]];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.Radius = function(arc, d) {
  this.arc = arc;
  this.d = d;
};

Constraints.Radius.prototype.NAME = 'Radius';
Constraints.Radius.prototype.UI_NAME = 'Radius Value';


Constraints.Radius.prototype.getSolveData = function(resolver) {
  return [['equalsTo', [this.arc.r], [resolver(this.d)]]];
};

Constraints.Radius.prototype.serialize = function() {
  return [this.NAME, [this.arc.id, this.d]];
};

Constraints.Factory[Constraints.Radius.prototype.NAME] = function(refs, data) {
  return new Constraints.Radius(refs(data[0]), data[1]);
};

Constraints.Radius.prototype.getObjects = function() {
  return [this.arc];
};

Constraints.Radius.prototype.SettableFields = {'d' : "Enter the radius value"};

// ------------------------------------------------------------------------------------------------------------------ // 

/** @constructor */
Constraints.RR = function(arc1, arc2) {
  this.arc1 = arc1;
  this.arc2 = arc2;
};

Constraints.RR.prototype.NAME = 'RR';
Constraints.RR.prototype.UI_NAME = 'Radius Equality';
//Constraints.RR.prototype.reducible = true;


Constraints.RR.prototype.getSolveData = function() {
  return [['equal', [this.arc1.r, this.arc2.r], []]];
};

Constraints.RR.prototype.serialize = function() {
  return [this.NAME, [this.arc1.id, this.arc2.id]];
};

Constraints.Factory[Constraints.RR.prototype.NAME] = function(refs, data) {
  return new Constraints.RR(refs(data[0]), refs(data[1]));
};

Constraints.RR.prototype.getObjects = function() {
  return [this.arc1, this.arc2];
};

// ------------------------------------------------------------------------------------------------------------------ // 

/** @constructor */
Constraints.LL = function(line1, line2) {
  this.line1 = line1;
  this.line2 = line2;
  this.length = new Ref(distanceAB(line1.a, line1.b));
};

Constraints.LL.prototype.NAME = 'LL';
Constraints.LL.prototype.UI_NAME = 'Lines Equality';

Constraints.LL.prototype.getSolveData = function() {
  const params1 = [];
  const params2 = [];
  this.line1.collectParams(params1);
  this.line2.collectParams(params2);
  params1.push(this.length);
  params2.push(this.length);
  return [
    ['P2PDistanceV', params1, []],
    ['P2PDistanceV', params2, []]
  ];
};

Constraints.LL.prototype.serialize = function() {
  return [this.NAME, [this.line1.id, this.line2.id]];
};

Constraints.Factory[Constraints.LL.prototype.NAME] = function(refs, data) {
  return new Constraints.LL(refs(data[0]), refs(data[1]));
};

Constraints.LL.prototype.getObjects = function() {
  return [this.line1, this.line2];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.Vertical = function(line) {
  this.line = line;
};

Constraints.Vertical.prototype.NAME = 'Vertical';
Constraints.Vertical.prototype.UI_NAME = 'Vertical';
//Constraints.Vertical.prototype.reducible = true;

Constraints.Vertical.prototype.getSolveData = function() {
  return [['equal', [this.line.a._x, this.line.b._x], []]];
};

Constraints.Vertical.prototype.serialize = function() {
  return [this.NAME, [this.line.id]];
};

Constraints.Factory[Constraints.Vertical.prototype.NAME] = function(refs, data) {
  return new Constraints.Vertical(refs(data[0]));
};

Constraints.Vertical.prototype.getObjects = function() {
  return [this.line];
};

// ------------------------------------------------------------------------------------------------------------------ // 

/** @constructor */
Constraints.Horizontal = function(line) {
  this.line = line;
};

Constraints.Horizontal.prototype.NAME = 'Horizontal';
Constraints.Horizontal.prototype.UI_NAME = 'Horizontal';
//Constraints.Horizontal.prototype.reducible = true;

Constraints.Horizontal.prototype.getSolveData = function() {
  return [['equal', [this.line.a._y, this.line.b._y], []]];
};

Constraints.Horizontal.prototype.serialize = function() {
  return [this.NAME, [this.line.id]];
};

Constraints.Factory[Constraints.Horizontal.prototype.NAME] = function(refs, data) {
  return new Constraints.Horizontal(refs(data[0]));
};

Constraints.Horizontal.prototype.getObjects = function() {
  return [this.line];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.Tangent = function(arc, line) {
  this.arc = arc;
  this.line = line;
};

Constraints.Tangent.prototype.NAME = 'Tangent';
Constraints.Tangent.prototype.UI_NAME = 'Tangent';

Constraints.Tangent.prototype.getSolveData = function() {
  const params = [];
  this.arc.c.collectParams(params);
  this.line.collectParams(params);
  params.push(this.arc.r);
  return [['P2LDistanceV', params, []]];
};

Constraints.Tangent.prototype.serialize = function() {
  return [this.NAME, [this.arc.id, this.line.id]];
};

Constraints.Factory[Constraints.Tangent.prototype.NAME] = function(refs, data) {
  return new Constraints.Tangent(refs(data[0]), refs(data[1]));
};

Constraints.Tangent.prototype.getObjects = function() {
  return [this.arc, this.line];
};

// ------------------------------------------------------------------------------------------------------------------ //

export class SignedPerpendicular extends AbstractConstraint {

  static deserialize(refs, data) {
    return new SignedPerpendicular(refs(data[0]), refs(data[1]), refs(data[2]), refs(data[3]));
  }

  constructor(p1, p2, p3, p4) {
    super();
    this.points = [p1, p2, p3, p4];
  }


  getSolveData() {
    const params = [];
    this.points.forEach(p => p.collectParams(params));
    return [['signedPerpendicular', params, []]];
  }

  serialize() {
    return [this.NAME, this.points.map(p => p.id)];
  }

  getObjects() {
    const collector = new Constraints.ParentsCollector();
    this.points.forEach(p =>  collector.check(p));
    return collector.parents;
  }
}

Constraints.SignedPerpendicular = SignedPerpendicular;
Constraints.SignedPerpendicular.prototype.NAME = 'SignedPerpendicular';
Constraints.SignedPerpendicular.prototype.UI_NAME = 'SignedPerpendicular';

Constraints.Factory[SignedPerpendicular.prototype.NAME] = Coincident.deserialize;


// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.PointOnLine = function(point, line) {
  this.point = point;
  this.line = line;
};

Constraints.PointOnLine.prototype.NAME = 'PointOnLine';
Constraints.PointOnLine.prototype.UI_NAME = 'Point On Line';

Constraints.PointOnLine.prototype.getSolveData = function() {
  const params = [];
  this.point.collectParams(params);
  this.line.collectParams(params);
  return [['P2LDistance', params, [0]]];
};

Constraints.PointOnLine.prototype.serialize = function() {
  return [this.NAME, [this.point.id, this.line.id]];
};

Constraints.Factory[Constraints.PointOnLine.prototype.NAME] = function(refs, data) {
  return new Constraints.PointOnLine(refs(data[0]), refs(data[1]));
};

Constraints.PointOnLine.prototype.getObjects = function() {
  return [this.point, this.line];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.PointOnArc = function(point, arc) {
  this.point = point;
  this.arc = arc;
};

Constraints.PointOnArc.prototype.NAME = 'PointOnArc';
Constraints.PointOnArc.prototype.UI_NAME = 'Point On Arc';

Constraints.PointOnArc.prototype.getSolveData = function() {
  const params = [];
  this.point.collectParams(params);
  this.arc.c.collectParams(params);
  params.push(this.arc.r);
  return [['P2PDistanceV', params, []]];
};

Constraints.PointOnArc.prototype.serialize = function() {
  return [this.NAME, [this.point.id, this.arc.id]];
};

Constraints.Factory[Constraints.PointOnArc.prototype.NAME] = function(refs, data) {
  return new Constraints.PointOnArc(refs(data[0]), refs(data[1]));
};

Constraints.PointOnArc.prototype.getObjects = function() {
  return [this.point, this.arc];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.PointOnCurve = function(point, curveObject) {
  this.point = point;
  this.curveObject = curveObject;
};

Constraints.PointOnCurve.prototype.NAME = 'PointOnCurve';
Constraints.PointOnCurve.prototype.UI_NAME = 'Point On Curve';

Constraints.PointOnCurve.prototype.getSolveData = function() {
  const params = [];
  this.point.collectParams(params);
  return [['PointOnCurve', params, [this.curveObject.curve]]];
};

Constraints.PointOnCurve.prototype.serialize = function() {
  return [this.NAME, [this.point.id, this.curveObject.id]];
};

Constraints.Factory[Constraints.PointOnCurve.prototype.NAME] = function(refs, data) {
  return new Constraints.PointOnCurve(refs(data[0]), refs(data[1]));
};

Constraints.PointOnCurve.prototype.getObjects = function() {
  return [this.point, this.curveObject];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.PointOnEllipseInternal = function(point, ellipse) {
  this.point = point;
  this.ellipse= ellipse;
};

Constraints.PointOnEllipseInternal.prototype.NAME = 'PointOnEllipseI';
Constraints.PointOnEllipseInternal.prototype.UI_NAME = 'Point On Ellipse';
Constraints.PointOnEllipseInternal.prototype.aux = true;

Constraints.PointOnEllipseInternal.prototype.getSolveData = function() {
  const params = [];
  this.point.collectParams(params);
  this.ellipse.ep1.collectParams(params);
  this.ellipse.ep2.collectParams(params);
  params.push(this.ellipse.r);
  return [['PointOnEllipse', params, []]];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.PointOnEllipse = function(point, ellipse) {
  Constraints.PointOnEllipseInternal.call(this, point, ellipse);
};

Constraints.PointOnEllipse.prototype.NAME = 'PointOnEllipse';
Constraints.PointOnEllipse.prototype.UI_NAME = 'Point On Ellipse';

Constraints.PointOnEllipse.prototype.getSolveData = function() {
  return Constraints.PointOnEllipseInternal.prototype.getSolveData.call(this);
};

Constraints.PointOnEllipse.prototype.serialize = function() {
  return [this.NAME, [this.point.id, this.ellipse.id]];
};

Constraints.Factory[Constraints.PointOnEllipse.prototype.NAME] = function(refs, data) {
  return new Constraints.PointOnEllipse(refs(data[0]), refs(data[1]));
};

Constraints.PointOnEllipse.prototype.getObjects = function() {
  return [this.point, this.ellipse];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.EllipseTangent = function(line, ellipse) {
  this.line = line;
  this.ellipse = ellipse;
};

Constraints.EllipseTangent.prototype.NAME = 'EllipseTangent';
Constraints.EllipseTangent.prototype.UI_NAME = 'Tangent Ellipse';

Constraints.EllipseTangent.prototype.getSolveData = function() {
  const params = [];
  this.line.collectParams(params);
  this.ellipse.ep1.collectParams(params);
  this.ellipse.ep2.collectParams(params);
  params.push(this.ellipse.r);
  return [['EllipseTangent', params, []]];

};

Constraints.EllipseTangent.prototype.serialize = function() {
  return [this.NAME, [this.line.id, this.ellipse.id]];
};

Constraints.Factory[Constraints.EllipseTangent.prototype.NAME] = function(refs, data) {
  return new Constraints.EllipseTangent(refs(data[0]), refs(data[1]));
};

Constraints.EllipseTangent.prototype.getObjects = function() {
  return [this.line, this.ellipse];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.CurveTangent = function(line, curveObject) {
  this.line = line;
  this.curveObject = curveObject;
  const [uMin, uMax] = this.curveObject.curve.domain();
  const initPoint = this.curveObject.curve.point(0.5 * (uMin + uMax));
  this.tx = new Ref(initPoint[0]);
  this.ty = new Ref(initPoint[1]);
};

Constraints.CurveTangent.prototype.NAME = 'CurveTangent';
Constraints.CurveTangent.prototype.UI_NAME = 'Curve Curve';

Constraints.CurveTangent.prototype.getSolveData = function() {
  const params = [];
  this.line.collectParams(params);
  params.push(this.tx);
  params.push(this.ty);
  return [['CurveTangent', params, [this.curveObject.curve]]];

};

Constraints.CurveTangent.prototype.serialize = function() {
  return [this.NAME, [this.line.id, this.curveObject.id]];
};

Constraints.Factory[Constraints.CurveTangent.prototype.NAME] = function(refs, data) {
  return new Constraints.CurveTangent(refs(data[0]), refs(data[1]));
};

Constraints.CurveTangent.prototype.getObjects = function() {
  return [this.line, this.curveObject];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.PointInMiddle = function(point, line) {
  this.point = point;
  this.line = line;
  this.length = new Ref(distanceAB(line.a, line.b) / 2);
};

Constraints.PointInMiddle.prototype.NAME = 'PointInMiddle';
Constraints.PointInMiddle.prototype.UI_NAME = 'Point In the Middle';

Constraints.PointInMiddle.prototype.getSolveData = function() {
  const params1 = [];
  const params2 = [];

  this.line.a.collectParams(params1);
  this.point.collectParams(params1);
  params1.push(this.length);

  this.line.b.collectParams(params2);
  this.point.collectParams(params2);
  params2.push(this.length);

  return [
    ['P2PDistanceV', params1, []],
    ['P2PDistanceV', params2, []]
  ];
};

Constraints.PointInMiddle.prototype.serialize = function() {
  return [this.NAME, [this.point.id, this.line.id]];
};

Constraints.Factory[Constraints.PointInMiddle.prototype.NAME] = function(refs, data) {
  return new Constraints.PointInMiddle(refs(data[0]), refs(data[1]));
};

Constraints.PointInMiddle.prototype.getObjects = function() {
  return [this.point, this.line];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.Symmetry = function(point, line) {
  this.point = point;
  this.line = line;
  this.length = new Ref(distanceAB(line.a, line.b) / 2);
};

Constraints.Symmetry.prototype.NAME = 'Symmetry';
Constraints.Symmetry.prototype.UI_NAME = 'Symmetry';

Constraints.Symmetry.prototype.getSolveData = function(resolver) {
  const pointInMiddleData = Constraints.PointInMiddle.prototype.getSolveData.call(this, [resolver]);
  const pointOnLineData = Constraints.PointOnLine.prototype.getSolveData.call(this, [resolver]);
  return pointInMiddleData.concat(pointOnLineData);
};

Constraints.Symmetry.prototype.serialize = function() {
  return [this.NAME, [this.point.id, this.line.id]];
};

Constraints.Factory[Constraints.Symmetry.prototype.NAME] = function(refs, data) {
  return new Constraints.Symmetry(refs(data[0]), refs(data[1]));
};

Constraints.Symmetry.prototype.getObjects = function() {
  return [this.point, this.line];
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.Angle = function(p1, p2, p3, p4, angle) {
  this.p1 = p1;
  this.p2 = p2;
  this.p3 = p3;
  this.p4 = p4;
  this._angle = new Ref(0);
  this.angle = angle;
};

Constraints.Angle.prototype.NAME = 'Angle';
Constraints.Angle.prototype.UI_NAME = 'Lines Angle';

Constraints.Angle.prototype.getSolveData = function(resolver) {
  this._angle.set(resolver(this.angle) / 180 * Math.PI);
  const params = [];
  this.p1.collectParams(params);
  this.p2.collectParams(params);
  this.p3.collectParams(params);
  this.p4.collectParams(params);
  params.push(this._angle);
  return [['angleConst', params, []]];
};

Constraints.Angle.prototype.serialize = function() {
  return [this.NAME, [this.p1.id, this.p2.id, this.p3.id, this.p4.id, this.angle]];
};

Constraints.Factory[Constraints.Angle.prototype.NAME] = function(refs, data) {
  return new Constraints.Angle( refs(data[0]), refs(data[1]), refs(data[2]), refs(data[3]), data[4] );
};

Constraints.Angle.prototype.getObjects = function() {
  const collector = new Constraints.ParentsCollector();
  collector.check(this.p1);
  collector.check(this.p2);
  collector.check(this.p3);
  collector.check(this.p4);
  return collector.parents;
};

Constraints.Angle.prototype.SettableFields = {'angle' : "Enter the angle value"};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.LockConvex = function(c, a, t) {
  this.c = c;
  this.a = a;
  this.t = t;
};

Constraints.LockConvex.prototype.NAME = 'LockConvex';
Constraints.LockConvex.prototype.UI_NAME = 'Lock Convexity';

Constraints.LockConvex.prototype.getSolveData = function() {
  const params = [];
  this.c.collectParams(params);
  this.a.collectParams(params);
  this.t.collectParams(params);
  return [['LockConvex', params, []]];
};

Constraints.LockConvex.prototype.serialize = function() {
  return [this.NAME, [this.c.id, this.a.id, this.t.id]];
};

Constraints.Factory[Constraints.LockConvex.prototype.NAME] = function(refs, data) {
  return new Constraints.LockConvex(refs(data[0]), refs(data[1]), refs(data[2]));
};

Constraints.LockConvex.prototype.getObjects = function() {
  const collector = new Constraints.ParentsCollector();
  collector.check(this.c);
  collector.check(this.a);
  collector.check(this.t);
  return collector.parents;
};


// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.Mirror = function(reflectionLine, objects, reflectedObjects) {
  this.reflectionLine = reflectionLine;
  this.objects = objects;
  this.dir = new Vector();
  this.updateDir();
  if (!reflectedObjects) {
    reflectedObjects = objects.map(o => {
      const copy = o.copy();
      copy.virtualOf = o.id;
      copy.aux = true;
      copy.role = 'virtual';
      o.layer.add(copy);
      this.reflect(o, copy);
      return copy;
    });
  }
  reflectedObjects.forEach((copy, i) => {
    copy.virtualOf = this.objects[i].id;
    copy.aux = true;
    copy.role = 'virtual';
  });
  this.reflectedObjects = reflectedObjects;
};

Constraints.Mirror.prototype.NAME = 'Mirror';
Constraints.Mirror.prototype.UI_NAME = 'Mirror';
Constraints.Mirror.prototype.GENERATOR = true;

Constraints.Mirror.prototype.updateDir = function() {
  this.dir.set(-(this.reflectionLine.b._y.get() - this.reflectionLine.a._y.get()), this.reflectionLine.b._x.get() - this.reflectionLine.a._x.get(), 0)._normalize();
};

Constraints.Mirror.prototype.reflect = function(source, dest) {
  const origin = this.reflectionLine.a.toVector();

  const pointMirroring = (x, y) => {
    const pt = new Vector(x, y, 0);
    const proj = this.dir.dot(pt.minus(origin));
    return this.dir.multiply(- proj * 2)._plus(pt);
  };

  source.mirror(dest, pointMirroring);
};

Constraints.Mirror.prototype.getSolveData = function() {
  return [];
};

Constraints.Mirror.prototype.serialize = function() {
  const ids = [this.reflectionLine.id];
  for (let i = 0; i < this.objects.length; i++) {
    ids.push(this.objects[i].id);
    ids.push(this.reflectedObjects[i].id);
  }
  return [this.NAME, ids];
};

Constraints.Factory[Constraints.Mirror.prototype.NAME] = function(refs, data) {
  const [rlId, ...objectIds] = data;
  const objects = [];
  const reflectedObjects = [];
  for (let i = 0; i < objectIds.length; i += 2) {
    objects.push(refs(objectIds[i]));
    reflectedObjects.push(refs(objectIds[i + 1]));
  }
  return new Constraints.Mirror(refs(rlId), objects, reflectedObjects);
};

Constraints.Mirror.prototype.getObjects = function() {
  return [this.reflectionLine, ...this.objects];
};

Constraints.Mirror.prototype.visitParams = function(callback) {
  this.reflectionLine.visitParams(callback);
  this.objects.forEach(o => o.visitParams(callback));
};

Constraints.Mirror.prototype.visitGeneratedParams = function(callback) {
  this.reflectedObjects.forEach(o => o.visitParams(callback));
};


Constraints.Mirror.prototype.updateGeneratedObjects = function() {
  this.updateDir();
  for (let i = 0; i < this.objects.length; i++) {
    this.reflect(this.objects[i], this.reflectedObjects[i]);
  }
};

Constraints.Mirror.prototype.getGeneratedObjects = function() {
  return this.reflectedObjects;
};

// ------------------------------------------------------------------------------------------------------------------ //

/** @constructor */
Constraints.Fillet = function(point1, point2, arc) {
  this.point1 = point1;
  this.point2 = point2;
  this.arc = arc;

  const line1 = point1.parent;
  const line2 = point2.parent;

  this.contraints = [
    new Constraints.SignedPerpendicular( arc.a, arc.c, point1, line1.opposite(point1)),
    new Constraints.SignedPerpendicular( arc.b, arc.c, line2.opposite(point2), point2),
    new Constraints.Coincident( arc.a, point1),
    new Constraints.Coincident( arc.b, point2)
  ];
};

Constraints.Fillet.prototype.NAME = 'Fillet';
Constraints.Fillet.prototype.UI_NAME = 'Fillet';

Constraints.Fillet.prototype.getSolveData = function() {
  const solveData = [];
  this.contraints.forEach(c => c.getSolveData().forEach(d => solveData.push(d)));
  return solveData;
};

Constraints.Fillet.prototype.serialize = function() {
  return [this.NAME, [this.point1.id, this.point2.id, this.arc.id]];
};

Constraints.Factory[Constraints.Fillet.prototype.NAME] = function(refs, data) {
  return new Constraints.Fillet(refs(data[0]), refs(data[1]), refs(data[2]));
};

Constraints.Fillet.prototype.getObjects = function() {
  const objects = [];
  this.contraints.forEach(c => c.getObjects().forEach(o => objects.push(o)));
  return objects;
};


Constraints.Fillet.prototype.validate = function() {

  function validOn(p, arc, left) {
    const op = p.parent.opposite(p);
    const opV = op.toVector();
    const dir = p.toVector()._minus(opV)._normalize();
    const centerDir = arc.c.toVector()._minus(opV)._normalize();
    const z = centerDir.cross(dir).z;

    return left === z < 0;
  }
  return validOn(this.point1, this.arc, true) && validOn(this.point2, this.arc, false);
};


Constraints.ParentsCollector = function() {
  this.parents = [];
  const parents = this.parents;
  const index = {};
  function add(obj) {
    if (index[obj.id] === undefined) {
      index[obj.id] = obj;
      parents.push(obj);
    }
  }
  this.check = function(obj) {
    if (obj.parent !== null) {
      add(obj.parent);
    } else {
      add(obj);
    }
  };
};

