package semaine08;

object week8 extends Application {

  type Action = () => unit;
  
  class Simulator {
    
    type Agenda = List[Pair[int, Action]];
    
    private var agenda: Agenda = List();
    
    private var curtime = 0;
    
    def afterDelay(delay: int)(action: Action): unit = {
      // ... ŕ compléter ...
    }

    protected def next: unit = agenda match {
      // ... ŕ compléter ...
    }

    protected def currentTime: int = curtime;

    def run: unit = {
      Console.println("*** New propagation ***");
      while (!agenda.isEmpty) { next }
    }
  }

  class Wire {

    private var sigVal = false;

    private var actions: List[Action] = List();

    def getSignal: boolean = sigVal;

    def setSignal(s: Boolean): unit =
      if (s != sigVal) {
        sigVal = s;
        actions.foreach(action => action());
      }

    def addAction(a: Action): unit = {
      actions = a :: actions;
      a()
    }
  }

  abstract class CircuitSimulator extends Simulator {

    val InverterDelay: int;
    val AndGateDelay: int;
    val OrGateDelay: int;

    def probe(name: String, wire: Wire): unit =
      wire addAction {
        () => afterDelay(0) {
          () => Console.println(
            "  " + currentTime + ": " + name + " -> " +  wire.getSignal)
        }
      };

    def inverter(input: Wire, output: Wire) = {
      def invertAction() = {
        val inputSig = input.getSignal;
        afterDelay(InverterDelay) { () => output.setSignal(!inputSig) };
      }
      input addAction invertAction
    }

    def andGate(a1: Wire, a2: Wire, output: Wire) = {
      def andAction() = {
        val a1Sig = a1.getSignal;
        val a2Sig = a2.getSignal;
        afterDelay(AndGateDelay) { () => output.setSignal(a1Sig & a2Sig) };
      }
      a1 addAction andAction;
      a2 addAction andAction
    }
    
    // ... ŕ compléter ...
  }
  
  object concreteCircuit extends CircuitSimulator {
    val InverterDelay = 1;
    val AndGateDelay = 3;
    val OrGateDelay = 5;
    
    def andGateExample = {
      val in1, in2, out = new Wire;
      andGate(in1, in2, out);
      probe("in1", in1);
      probe("in2", in2);
      probe("out", out);      
      in1.setSignal(false);
      in2.setSignal(false);
      run;

      in1.setSignal(true);
      run;

      in2.setSignal(true);
      run;
    }

    // Other examples

  }

   concreteCircuit.andGateExample;

}
