library;
import self as self;
import "dart:core" as core;

class DeltaBlue extends core::Object {
  constructor •() → void
    : super core::Object::•()
    ;
  method run() → void/Null {
    self::chainTest(100);
    self::projectionTest(100);
  }
}
class Strength extends core::Object {
  final field core::int/core::int* {int} value;
  final field core::String/core::String* {string} name;
  const constructor •(core::int/core::int* {int} value, core::String/core::String* {string} name) → void
    : self::Strength::value = value, self::Strength::name = name, super core::Object::•()
    ;
  method nextWeaker() → self::Strength/core::Object+ {*}
    return const <self::Strength>[self::STRONG_PREFERRED, self::PREFERRED, self::STRONG_DEFAULT, self::NORMAL, self::WEAK_DEFAULT, self::WEAKEST].[](this.value);
  static method stronger(self::Strength/self::Strength! {other} s1, self::Strength/self::Strength! {other} s2) → core::bool/core::bool* {other} {
    return s1.value.<(s2.value);
  }
  static method weaker(self::Strength/self::Strength! {other} s1, self::Strength/self::Strength! {other} s2) → core::bool/core::bool* {other} {
    return s1.value.>(s2.value);
  }
  static method weakest(self::Strength/self::Strength! {other} s1, self::Strength/self::Strength! {other} s2) → self::Strength/self::Strength! {other} {
    return self::Strength::weaker(s1, s2) ? s1 : s2;
  }
  static method strongest(self::Strength/Nothing s1, self::Strength/Nothing s2) → self::Strength/Nothing {
    return self::Strength::stronger(s1, s2) ? s1 : s2;
  }
}
abstract class Constraint extends core::Object {
  final field self::Strength/self::Strength! {other} strength;
  const constructor •(self::Strength/self::Strength! {other} strength) → void
    : self::Constraint::strength = strength, super core::Object::•()
    ;
  abstract method isSatisfied() → core::bool/<missing type>;
  abstract method markUnsatisfied() → void/<missing type>;
  abstract method addToGraph() → void/<missing type>;
  abstract method removeFromGraph() → void/<missing type>;
  abstract method chooseMethod(core::int/<missing type> mark) → void/<missing type>;
  abstract method markInputs(core::int/<missing type> mark) → void/<missing type>;
  abstract method inputsKnown(core::int/<missing type> mark) → core::bool/<missing type>;
  abstract method output() → self::Variable/<missing type>;
  abstract method execute() → void/<missing type>;
  abstract method recalculate() → void/<missing type>;
  method addConstraint() → void/Null {
    this.addToGraph();
    self::planner.incrementalAdd(this);
  }
  method satisfy(dynamic/core::num* {int,double} mark) → self::Constraint/self::Constraint+ {null,other} {
    this.chooseMethod(mark);
    if(!this.isSatisfied()) {
      if(this.strength.==(self::REQUIRED)) {
        core::print("Could not satisfy a required constraint!");
      }
      return null;
    }
    this.markInputs(mark);
    self::Variable/self::Variable! {null,other} out = this.output();
    self::Constraint/self::Constraint+ {null,other} overridden = out.determinedBy;
    if(!overridden.==(null))
      overridden.markUnsatisfied();
    out.determinedBy = this;
    if(!self::planner.addPropagate(this, mark))
      core::print("Cycle encountered");
    out.mark = mark;
    return overridden;
  }
  method destroyConstraint() → void/Null {
    if(this.isSatisfied())
      self::planner.incrementalRemove(this);
    this.removeFromGraph();
  }
  method isInput() → core::bool/core::bool* {other}
    return false;
}
abstract class UnaryConstraint extends self::Constraint {
  final field self::Variable/self::Variable! {null,other} myOutput;
  field core::bool/core::bool* {other} satisfied = false;
  constructor •(self::Variable/self::Variable! {null,other} myOutput, self::Strength/self::Strength! {other} strength) → void
    : self::UnaryConstraint::myOutput = myOutput, super self::Constraint::•(strength) {
    this.addConstraint();
  }
  method addToGraph() → void/Null {
    this.myOutput.addConstraint(this);
    this.satisfied = false;
  }
  method chooseMethod(core::int/core::num* {int,double} mark) → void/Null {
    this.satisfied = !this.myOutput.mark.==(mark) && self::Strength::stronger(this.strength, this.myOutput.walkStrength);
  }
  method isSatisfied() → core::bool/core::bool* {other}
    return this.satisfied;
  method markInputs(core::int/core::num* {int,double} mark) → void/Null {}
  method output() → self::Variable/self::Variable! {null,other}
    return this.myOutput;
  method recalculate() → void/Null {
    this.myOutput.walkStrength = this.strength;
    this.myOutput.stay = !this.isInput();
    if(this.myOutput.stay)
      this.execute();
  }
  method markUnsatisfied() → void/Null {
    this.satisfied = false;
  }
  method inputsKnown(core::int/core::num* {int,double} mark) → core::bool/core::bool* {other}
    return true;
  method removeFromGraph() → void/Null {
    if(!this.myOutput.==(null))
      this.myOutput.removeConstraint(this);
    this.satisfied = false;
  }
}
class StayConstraint extends self::UnaryConstraint {
  constructor •(self::Variable/self::Variable! {null,other} v, self::Strength/self::Strength! {other} str) → void
    : super self::UnaryConstraint::•(v, str)
    ;
  method execute() → void/Null {}
}
class EditConstraint extends self::UnaryConstraint {
  constructor •(self::Variable/self::Variable! {null,other} v, self::Strength/self::Strength! {other} str) → void
    : super self::UnaryConstraint::•(v, str)
    ;
  method isInput() → core::bool/core::bool* {other}
    return true;
  method execute() → void/Null {}
}
abstract class BinaryConstraint extends self::Constraint {
  field self::Variable/self::Variable! {null,other} v1;
  field self::Variable/self::Variable! {null,other} v2;
  field core::int/core::int* {int} direction = self::NONE;
  constructor •(self::Variable/self::Variable! {null,other} v1, self::Variable/self::Variable! {null,other} v2, self::Strength/self::Strength! {other} strength) → void
    : self::BinaryConstraint::v1 = v1, self::BinaryConstraint::v2 = v2, super self::Constraint::•(strength) {
    this.addConstraint();
  }
  method chooseMethod(core::int/core::num* {int,double} mark) → void/Null {
    if(this.v1.mark.==(mark)) {
      this.direction = !this.v2.mark.==(mark) && self::Strength::stronger(this.strength, this.v2.walkStrength) ? self::FORWARD : self::NONE;
    }
    if(this.v2.mark.==(mark)) {
      this.direction = !this.v1.mark.==(mark) && self::Strength::stronger(this.strength, this.v1.walkStrength) ? self::BACKWARD : self::NONE;
    }
    if(self::Strength::weaker(this.v1.walkStrength, this.v2.walkStrength)) {
      this.direction = self::Strength::stronger(this.strength, this.v1.walkStrength) ? self::BACKWARD : self::NONE;
    }
    else {
      this.direction = self::Strength::stronger(this.strength, this.v2.walkStrength) ? self::FORWARD : self::BACKWARD;
    }
  }
  method addToGraph() → void/Null {
    this.v1.addConstraint(this);
    this.v2.addConstraint(this);
    this.direction = self::NONE;
  }
  method isSatisfied() → core::bool/core::bool* {other}
    return !this.direction.==(self::NONE);
  method markInputs(core::int/core::num* {int,double} mark) → void/Null {
    this.input().mark = mark;
  }
  method input() → self::Variable/self::Variable! {null,other}
    return this.direction.==(self::FORWARD) ? this.v1 : this.v2;
  method output() → self::Variable/self::Variable! {null,other}
    return this.direction.==(self::FORWARD) ? this.v2 : this.v1;
  method recalculate() → void/Null {
    self::Variable/self::Variable! {null,other} ihn = this.input();
    self::Variable/self::Variable! {null,other} out = this.output();
    out.walkStrength = self::Strength::weakest(this.strength, ihn.walkStrength);
    out.stay = ihn.stay;
    if(out.stay)
      this.execute();
  }
  method markUnsatisfied() → void/Null {
    this.direction = self::NONE;
  }
  method inputsKnown(core::int/core::num* {int,double} mark) → core::bool/core::bool* {other} {
    self::Variable/self::Variable! {null,other} i = this.input();
    return i.mark.==(mark) || i.stay || i.determinedBy.==(null);
  }
  method removeFromGraph() → void/Null {
    if(!this.v1.==(null))
      this.v1.removeConstraint(this);
    if(!this.v2.==(null))
      this.v2.removeConstraint(this);
    this.direction = self::NONE;
  }
}
class ScaleConstraint extends self::BinaryConstraint {
  final field self::Variable/self::Variable! {other} scale;
  final field self::Variable/self::Variable! {other} offset;
  constructor •(self::Variable/self::Variable! {null,other} src, self::Variable/self::Variable! {other} scale, self::Variable/self::Variable! {other} offset, self::Variable/self::Variable! {null,other} dest, self::Strength/self::Strength! {other} strength) → void
    : self::ScaleConstraint::scale = scale, self::ScaleConstraint::offset = offset, super self::BinaryConstraint::•(src, dest, strength)
    ;
  method addToGraph() → void/Null {
    this.{=self::BinaryConstraint::addToGraph}();
    this.scale.addConstraint(this);
    this.offset.addConstraint(this);
  }
  method removeFromGraph() → void/Null {
    this.{=self::BinaryConstraint::removeFromGraph}();
    if(!this.scale.==(null))
      this.scale.removeConstraint(this);
    if(!this.offset.==(null))
      this.offset.removeConstraint(this);
  }
  method markInputs(core::int/core::num* {int,double} mark) → void/Null {
    this.{=self::BinaryConstraint::markInputs}(mark);
    this.scale.mark = this.offset.mark = mark;
  }
  method execute() → void/Null {
    if(this.direction.==(self::FORWARD)) {
      this.v2.value = this.v1.value.*(this.scale.value).+(this.offset.value);
    }
    else {
      this.v1.value = this.v2.value.-(this.offset.value).~/(this.scale.value);
    }
  }
  method recalculate() → void/Null {
    self::Variable/self::Variable! {null,other} ihn = this.input();
    self::Variable/self::Variable! {null,other} out = this.output();
    out.walkStrength = self::Strength::weakest(this.strength, ihn.walkStrength);
    out.stay = ihn.stay && this.scale.stay && this.offset.stay;
    if(out.stay)
      this.execute();
  }
}
class EqualityConstraint extends self::BinaryConstraint {
  constructor •(self::Variable/self::Variable! {null,other} v1, self::Variable/self::Variable! {other} v2, self::Strength/self::Strength! {other} strength) → void
    : super self::BinaryConstraint::•(v1, v2, strength)
    ;
  method execute() → void/Null {
    this.output().value = this.input().value;
  }
}
class Variable extends core::Object {
  field core::List<self::Constraint>/core::List* {other} constraints = <self::Constraint>[];
  field self::Constraint/self::Constraint+ {null,other} determinedBy = null;
  field core::int/core::num* {int,double} mark = 0;
  field self::Strength/self::Strength! {other} walkStrength = self::WEAKEST;
  field core::bool/core::bool* {other} stay = true;
  field core::int/core::num* {int,double} value;
  final field core::String/core::String* {string} name;
  constructor •(core::String/core::String* {string} name, core::int/core::num* {int,double} value) → void
    : self::Variable::name = name, self::Variable::value = value, super core::Object::•()
    ;
  method addConstraint(self::Constraint/self::Constraint+ {other} c) → void/Null {
    this.constraints.add(c);
  }
  method removeConstraint(self::Constraint/self::Constraint+ {other} c) → void/Null {
    this.constraints.remove(c);
    if(this.determinedBy.==(c))
      this.determinedBy = null;
  }
}
class Planner extends core::Object {
  field core::int/core::num* {int,double} currentMark = 0;
  constructor •() → void
    : super core::Object::•()
    ;
  method incrementalAdd(self::Constraint/core::Object+ {*} c) → void/Null {
    core::int/core::num* {int,double} mark = this.newMark();
    for (self::Constraint/self::Constraint+ {null,other} overridden = c.satisfy(mark); !overridden.==(null); overridden = overridden.satisfy(mark))
      ;
  }
  method incrementalRemove(self::Constraint/self::Constraint+ {other} c) → void/Null {
    self::Variable/self::Variable! {null,other} out = c.output();
    c.markUnsatisfied();
    c.removeFromGraph();
    core::List<self::Constraint>/core::List* {other} unsatisfied = this.removePropagateFrom(out);
    self::Strength/core::Object+ {*} strength = self::REQUIRED;
    do {
      for (core::int/core::num* {int,double} i = 0; i.<(unsatisfied.length); i = i.+(1)) {
        self::Constraint/core::Object+ {*} u = unsatisfied.[](i);
        if(u.strength.==(strength))
          this.incrementalAdd(u);
      }
      strength = strength.nextWeaker();
    }
    while (!strength.==(self::WEAKEST))
  }
  method newMark() → core::int/core::num* {int,double}
    return this.currentMark = this.currentMark.+(1);
  method makePlan(core::List<self::Constraint>/core::List* {other} sources) → self::Plan/self::Plan! {other} {
    core::int/core::num* {int,double} mark = this.newMark();
    self::Plan/self::Plan! {other} plan = new self::Plan::•();
    core::List<self::Constraint>/core::List* {other} todo = sources;
    while (todo.length.>(0)) {
      self::Constraint/core::Object+ {*} c = todo.removeLast();
      if(!c.output().mark.==(mark) && c.inputsKnown(mark)) {
        plan.addConstraint(c);
        c.output().mark = mark;
        this.addConstraintsConsumingTo(c.output(), todo);
      }
    }
    return plan;
  }
  method extractPlanFromConstraints(core::List<self::Constraint>/core::List* {other} constraints) → self::Plan/self::Plan! {other} {
    core::List<self::Constraint>/core::List* {other} sources = <self::Constraint>[];
    for (core::int/core::num* {int,double} i = 0; i.<(constraints.length); i = i.+(1)) {
      self::Constraint/core::Object+ {*} c = constraints.[](i);
      if(c.isInput() && c.isSatisfied())
        sources.add(c);
    }
    return this.makePlan(sources);
  }
  method addPropagate(self::Constraint/self::Constraint+ {other} c, core::int/core::num* {int,double} mark) → core::bool/core::bool* {other} {
    core::List<self::Constraint>/core::List* {other} todo = <self::Constraint>[c];
    while (todo.length.>(0)) {
      self::Constraint/core::Object+ {*} d = todo.removeLast();
      if(d.output().mark.==(mark)) {
        this.incrementalRemove(c);
        return false;
      }
      d.recalculate();
      this.addConstraintsConsumingTo(d.output(), todo);
    }
    return true;
  }
  method removePropagateFrom(self::Variable/self::Variable! {null,other} out) → core::List<self::Constraint>/core::List* {other} {
    out.determinedBy = null;
    out.walkStrength = self::WEAKEST;
    out.stay = true;
    core::List<self::Constraint>/core::List* {other} unsatisfied = <self::Constraint>[];
    core::List<self::Variable>/core::List* {other} todo = <self::Variable>[out];
    while (todo.length.>(0)) {
      self::Variable/core::Object+ {*} v = todo.removeLast();
      for (core::int/core::num* {int,double} i = 0; i.<(v.constraints.length); i = i.+(1)) {
        self::Constraint/core::Object+ {*} c = v.constraints.[](i);
        if(!c.isSatisfied())
          unsatisfied.add(c);
      }
      self::Constraint/self::Constraint+ {null,other} determining = v.determinedBy;
      for (core::int/core::num* {int,double} i = 0; i.<(v.constraints.length); i = i.+(1)) {
        self::Constraint/core::Object+ {*} next = v.constraints.[](i);
        if(!next.==(determining) && next.isSatisfied()) {
          next.recalculate();
          todo.add(next.output());
        }
      }
    }
    return unsatisfied;
  }
  method addConstraintsConsumingTo(self::Variable/self::Variable! {null,other} v, core::List<self::Constraint>/core::List* {other} coll) → void/Null {
    self::Constraint/self::Constraint+ {null,other} determining = v.determinedBy;
    for (core::int/core::num* {int,double} i = 0; i.<(v.constraints.length); i = i.+(1)) {
      self::Constraint/core::Object+ {*} c = v.constraints.[](i);
      if(!c.==(determining) && c.isSatisfied())
        coll.add(c);
    }
  }
}
class Plan extends core::Object {
  field core::List<self::Constraint>/core::List* {other} list = <self::Constraint>[];
  constructor •() → void
    : super core::Object::•()
    ;
  method addConstraint(self::Constraint/core::Object+ {*} c) → void/Null {
    this.list.add(c);
  }
  method size() → core::int/core::int* {int}
    return this.list.length;
  method execute() → void/Null {
    for (core::int/core::num* {int,double} i = 0; i.<(this.list.length); i = i.+(1)) {
      this.list.[](i).execute();
    }
  }
}
static const field dynamic/self::Strength! {other} REQUIRED = const self::Strength::•(0, "required");
static const field dynamic/self::Strength! {other} STRONG_PREFERRED = const self::Strength::•(1, "strongPreferred");
static const field dynamic/self::Strength! {other} PREFERRED = const self::Strength::•(2, "preferred");
static const field dynamic/self::Strength! {other} STRONG_DEFAULT = const self::Strength::•(3, "strongDefault");
static const field dynamic/self::Strength! {other} NORMAL = const self::Strength::•(4, "normal");
static const field dynamic/self::Strength! {other} WEAK_DEFAULT = const self::Strength::•(5, "weakDefault");
static const field dynamic/self::Strength! {other} WEAKEST = const self::Strength::•(6, "weakest");
static const field core::int/core::int* {int} NONE = 1;
static const field core::int/core::int* {int} FORWARD = 2;
static const field core::int/core::int* {int} BACKWARD = 0;
static field self::Planner/self::Planner! {null,other} planner = null;
static method main() → dynamic/Null {
  new self::DeltaBlue::•().run();
}
static method chainTest(core::int/core::int* {int} n) → void/Null {
  self::planner = new self::Planner::•();
  self::Variable/self::Variable! {null,other} prev = null;
  self::Variable/self::Variable! {null,other} first = null;
  self::Variable/self::Variable! {null,other} last = null;
  for (core::int/core::num* {int,double} i = 0; i.<=(n); i = i.+(1)) {
    self::Variable/self::Variable! {other} v = new self::Variable::•("v${i}", 0);
    if(!prev.==(null))
      new self::EqualityConstraint::•(prev, v, self::REQUIRED);
    if(i.==(0))
      first = v;
    if(i.==(n))
      last = v;
    prev = v;
  }
  new self::StayConstraint::•(last, self::STRONG_DEFAULT);
  self::EditConstraint/self::EditConstraint! {other} edit = new self::EditConstraint::•(first, self::PREFERRED);
  self::Plan/self::Plan! {other} plan = self::planner.extractPlanFromConstraints(<self::Constraint>[edit]);
  for (core::int/core::num* {int,double} i = 0; i.<(100); i = i.+(1)) {
    first.value = i;
    plan.execute();
    if(!last.value.==(i)) {
      core::print("Chain test failed:");
      core::print("Expected last value to be ${i} but it was ${last.value}.");
    }
  }
}
static method projectionTest(core::int/core::int* {int} n) → void/Null {
  self::planner = new self::Planner::•();
  self::Variable/self::Variable! {other} scale = new self::Variable::•("scale", 10);
  self::Variable/self::Variable! {other} offset = new self::Variable::•("offset", 1000);
  self::Variable/self::Variable! {null,other} src = null;
  self::Variable/self::Variable! {null,other} dst = null;
  core::List<self::Variable>/core::List* {other} dests = <self::Variable>[];
  for (core::int/core::num* {int,double} i = 0; i.<(n); i = i.+(1)) {
    src = new self::Variable::•("src", i);
    dst = new self::Variable::•("dst", i);
    dests.add(dst);
    new self::StayConstraint::•(src, self::NORMAL);
    new self::ScaleConstraint::•(src, scale, offset, dst, self::REQUIRED);
  }
  self::change(src, 17);
  if(!dst.value.==(1170))
    core::print("Projection 1 failed");
  self::change(dst, 1050);
  if(!src.value.==(5))
    core::print("Projection 2 failed");
  self::change(scale, 5);
  for (core::int/core::num* {int,double} i = 0; i.<(n.-(1)); i = i.+(1)) {
    if(!dests.[](i).value.==(i.*(5).+(1000)))
      core::print("Projection 3 failed");
  }
  self::change(offset, 2000);
  for (core::int/core::num* {int,double} i = 0; i.<(n.-(1)); i = i.+(1)) {
    if(!dests.[](i).value.==(i.*(5).+(2000)))
      core::print("Projection 4 failed");
  }
}
static method change(self::Variable/self::Variable! {null,other} v, core::int/core::int* {int} newValue) → void/Null {
  self::EditConstraint/self::EditConstraint! {other} edit = new self::EditConstraint::•(v, self::PREFERRED);
  self::Plan/self::Plan! {other} plan = self::planner.extractPlanFromConstraints(<self::EditConstraint>[edit]);
  for (core::int/core::num* {int,double} i = 0; i.<(10); i = i.+(1)) {
    v.value = newValue;
    plan.execute();
  }
  edit.destroyConstraint();
}
