object solver extends Application {

  trait Constraint {
    def newValue: unit;
    def dropValue: unit
  }
  
  object NoConstraint extends Constraint {
    def newValue: unit = error("NoConstraint.newValue");
    def dropValue: unit = error("NoConstraint.dropValue");
  }
  
  case class Adder(a1: Quantity, a2: Quantity, sum: Quantity) extends Constraint {
    def newValue: unit = Triple(a1.getValue, a2.getValue, sum.getValue) match {
      case Triple(Some(x1), Some(x2), _      ) => sum.setValue(x1 + x2, this)
      case Triple(Some(x1), _       , Some(r)) => a2.setValue(r - x1, this)
      case Triple(_       , Some(x2), Some(r)) => a1.setValue(r - x2, this)
      case _                                   => ()
    }
    def dropValue: unit = {
      a1.forgetValue(this); a2.forgetValue(this); sum.forgetValue(this);
    }
    a1 connect this;
    a2 connect this;
    sum connect this;
  }

  case class Constant(q: Quantity, v: double) extends Constraint {
    def newValue: unit = error("Constant.newValue");
    def dropValue: unit = error("Constant.dropValue");
    q connect this;
    q.setValue(v, this);
  }

  case class Probe(name: String, q: Quantity) extends Constraint {
    def newValue: unit = printProbe(q.getValue);
    def dropValue: unit = printProbe(None);
    private def printProbe(v: Option[double]): unit = {
      val vstr = v match {
        case Some(x) => x.toString()
        case None => "?"
      }
      Console.println("Probe: " + name + " = " + vstr);
    }
    q connect this
  }
  
  // COMPLETE HERE: define constraints for multiplication, equality and square.
  
  class Quantity {
    
    private var value: Option[double] = None;
    private var constraints: List[Constraint] = List();
    private var informant: Constraint = null;
    
    def getValue: Option[double] = value;
    
    def setValue(v: double, setter: Constraint): unit = value match {
      case Some(v1) =>
        if (v != v1) error("Error! contradiction: " + v + " and " + v1);
      case None => {
        informant = setter;
        value = Some(v);
        for (val c <- constraints; c != informant) c.newValue;
      }
    }
    def setValue(v: double): unit = setValue(v, NoConstraint);

    def forgetValue(retractor: Constraint): unit = {
      if (retractor == informant) {
        value = None;
        for (val c <- constraints; c != informant) c.dropValue;
      }
    }
    def forgetValue: unit = forgetValue(NoConstraint);
    
    def connect(c: Constraint): unit = {
      constraints = c :: constraints;
      value match {
        case Some(_) => c.newValue
        case None => ()
      }
    }
    
    def +(that: Quantity): Quantity = {
      val sum = new Quantity;
      Adder(this, that, sum);
      sum
    }
    
    // COMPLETE HERE: define operators "*" and "===".  
    
    override def toString(): String = value match {
      case None    => "  ?"
      case Some(v) => v.toString()
    }
  }

  // COMPLETE HERE: define a function "c" that creates a constant quantity.
  
  def adderExample = {
    val q1, q2, q3 = new Quantity;
    Adder(q1, q2, q3);
    Probe("q1", q1);
    Probe("q2", q2);
    Probe("q3", q3);
    
    q1.setValue(20);
    q2.setValue(22);
    q1.forgetValue;
    q2.forgetValue;
    Console.println("-----");
    
    q1.setValue(20);
    q3.setValue(22);
    q1.forgetValue;
    q3.forgetValue;
    Console.println("-----");
    
    q2.setValue(20);
    q3.setValue(22);
    q2.forgetValue;
    q3.forgetValue;
    Console.println("-----");
  }
  
  def converterExample = {
    // COMPLETE HERE: write the Celsius/Fahrenheit converter.
  }
  
  adderExample;
  converterExample;

}
