// Circuit Simulator for ICS 331 Assignment 2
// by David N. Chin
//
// Accepts as a command-line argument the name of a file that contains a
// digital circuit specification.  It reads the file, creates the Wire,
// Battery, and Gate instances specified in the file, then runs the
// simulation until no Wire values change.  Finally it prints out the values
// of all Wires.


import java.io.*;
import java.util.*;
import java.lang.*;
import java.util.regex.*;

class Circuit {
  protected static LinkedHashMap<String, Wire> allWires = new LinkedHashMap<String, Wire>();
  protected static List<Gate> allGates = new ArrayList<Gate>();
  protected static List<Battery> allBatteries = new ArrayList<Battery>();
  protected static boolean changed = false;
  public enum GateTypes { WIRE, BATTERY, NOTGATE, BUFFERGATE, ANDGATE, ORGATE };

  public static void main(String[] args) {
    if(args.length != 1) usage();
    readSpecs(args[0]);
    setBatteries();
    computeLoop();
    printWires();
  }

  private static void usage() {
    System.err.println("java Circuit <circuit spec file>.cir");
    System.exit(1);
  }

  private static void readSpecs(String specfile) {
    String gateString = "";
    try {
      File file = new File(specfile);
      Scanner scanner = new Scanner(file);
      while (scanner.hasNext()) {
	switch (GateTypes.valueOf(gateString = scanner.next())) {
	  case WIRE:       new Wire(scanner); break;
	  case BATTERY:    new Battery(scanner); break;
	  case NOTGATE:    new NotGate(scanner); break;
	  case BUFFERGATE: new BufferGate(scanner); break;
	  case ANDGATE:    new AndGate(scanner); break;
	  case ORGATE:     new OrGate(scanner); break;
	}
      }
      scanner.close();
    } catch (IllegalArgumentException e) {
      System.err.println("Unknown GATE type: "+gateString);
      System.exit(1);
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      e.printStackTrace();
    }
  }

  protected static Wire findWire(String name) {
    if (! allWires.containsKey(name)) {
      System.err.println("Cannot find a Wire with name "+name);
      System.exit(1);
    }
    return allWires.get(name);
  }

  // loop through all Gates and call compute on them until no output Wire
  // values are changed (may loop forever if there is an unstable feedback
  // loop in the circuit)
  private static void computeLoop() {
    while(changed) {
      changed = false;
      for( Gate gate : allGates ) {
	gate.compute();
      }
    }
  }

  // print all Wires
  private static void printWires() {
    for( Wire wire : allWires.values() ) {
	wire.print();
      }
    System.out.println();
  }

  // loop through all Batteries and set the values of all Wires connected to
  // the high terminals of the Battery to true
  private static void setBatteries() {
    for( Battery battery : allBatteries ) {
      battery.setWires();
    }
  }

}

/** base Circuit-Element class  */
abstract class CircuitElement {
}

class Wire extends CircuitElement {
  public String name; // the name of the Wire

  Wire(Scanner scanner) {
    scanner.next(); // skip "name"
    name = scanner.next(); // read the name
    Circuit.allWires.put(name, this); // add this Wire to allWires
  }

  private boolean value; // the wire value, which should only be read/set by:
  boolean get_value() { return value; }
  void set_value(boolean new_value) { // also set Circuit.changed to true
    if (value != new_value) {         // if the new value differs from the
      value = new_value;              // old value
      Circuit.changed = true;
    }
  }

  public void print() {
    System.out.print(name+"="+(value ? "1 " : "0 "));
  }
}

class Battery extends CircuitElement {
  // the Wire(s) connected to the +/high -/low terminals of this battery:
  private List<Wire> high = new ArrayList<Wire>();
  private List<Wire> low = new ArrayList<Wire>();

  Battery(Scanner scanner) {
    Circuit.allBatteries.add(this); // add this Battery to allBatteries

    // read the list of Wires connected to high
    scanner.findInLine("high \\(([\\w\\s]*)\\)");
    MatchResult result = scanner.match();
    Scanner scanParens = new Scanner(result.group(1));
    while (scanParens.hasNext()) {
      high.add(Circuit.findWire(scanParens.next()));
    }

    // read the list of Wires connected to low
    scanner.findInLine("low \\(([\\w\\s]*)\\)");
    result = scanner.match();
    scanParens = new Scanner(result.group(1));
    while (scanParens.hasNext()) {
      low.add(Circuit.findWire(scanParens.next()));
    }
  }

  public void addHigh(Wire w) {
    high.add(w);
  }
  public void addLow(Wire w) {
    low.add(w);
  }

  // set the values of all Wires attached to the Battery (since all Wire values
  // are initialized to false, only Wires attached to the high terminal need
  // to be set to true)
  public void setWires() {
    for( Wire wire : high ) {
      wire.set_value(true);
    }
  }
}

abstract class Gate extends CircuitElement {
  protected Wire output; // the Wire connected to the output of the Gate
  protected abstract void compute(); // set the value of the output Wire based
                                     // on the value(s) of the input(s)
}

/** Gates with only one input  */
abstract class UnaryGate extends Gate {
  protected Wire input; // the Wire connected to the input of the Gate

  UnaryGate(Scanner scanner) {
    Circuit.allGates.add(this); // add this Gate to allGates

    // read the input Wire
    scanner.findInLine("input (\\w+)");
    MatchResult result = scanner.match();
    input = Circuit.findWire(result.group(1));

    // read the output Wire
    scanner.findInLine("output (\\w+)");
    result = scanner.match();
    scanner.nextLine();
    output = Circuit.findWire(result.group(1));

  }
}

/** Gates with exactly two inputs  */
abstract class BinaryGate extends Gate {
  protected Wire input1; // the Wire connected to one input of the Gate
  protected Wire input2; // the Wire connected to the other input of the Gate

  BinaryGate(Scanner scanner) {
    Circuit.allGates.add(this); // add this Gate to allGates

    // read input1
    scanner.findInLine("input1 (\\w+)");
    MatchResult result = scanner.match();
    input1 = Circuit.findWire(result.group(1));

    // read input2
    scanner.findInLine("input2 (\\w+)");
    result = scanner.match();
    input2 = Circuit.findWire(result.group(1));

    // read output
    scanner.findInLine("output (\\w+)");
    result = scanner.match();
    output = Circuit.findWire(result.group(1));
  }
}

class NotGate extends UnaryGate {
  NotGate(Scanner scanner) {
    super(scanner);
  }

  protected void compute() {
    output.set_value(! input.get_value());
  }
}

class BufferGate extends UnaryGate {
  BufferGate(Scanner scanner) {
    super(scanner);
  }

  protected void compute() {
    output.set_value(input.get_value());
  }
}

class AndGate extends BinaryGate {
  AndGate(Scanner scanner) {
    super(scanner);
  }

  protected void compute() {
    output.set_value(input1.get_value() && input2.get_value());
    }
}

class OrGate extends BinaryGate {
  OrGate(Scanner scanner) {
    super(scanner);
  }

  protected void compute() {
    output.set_value(input1.get_value() || input2.get_value());
    }
}
