// Circuit Simulator for ICS 331 Assignment 4
// 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, Gate, SPDT Switch 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 List<SPDTSwitch> allSwitches = new ArrayList<SPDTSwitch>();
  protected static boolean changed = false;
  public enum GateTypes { WIRE, BATTERY, SPDTSWITCH, NOTGATE, BUFFERGATE,
			  ANDGATE, ORGATE, XORGATE, NANDGATE, NORGATE, XNORGATE,
			  AND3GATE, OR3GATE, XOR3GATE, NAND3GATE, NOR3GATE,
			  XNOR3GATE };

  public static void main(String[] args) {
    BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));

    if(args.length != 1) usage();
    readSpecs(args[0]);
    setBatteries();
    try{
      do {
	for (SPDTSwitch sw : allSwitches) {
	  sw.askConnect(stdin);
	}
	Circuit.changed = true;
	computeLoop();
	printWires();
	System.out.print("Repeat? (y or n): ");
	System.out.flush();
      } while ( stdin.readLine().equalsIgnoreCase("y") );
    } catch (IOException ioe) {
      System.out.println(ioe.toString());
      System.exit(1);
    }
  }

  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 SPDTSWITCH: new SPDTSwitch(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;
	  case XORGATE:    new XorGate(scanner); break;
	  case XNORGATE:   new XnorGate(scanner); break;
	  case NANDGATE:   new NandGate(scanner); break;
	  case NORGATE:    new NorGate(scanner); break;
	  case AND3GATE:   new And3Gate(scanner); break;
	  case OR3GATE:    new Or3Gate(scanner); break;
	  case XOR3GATE:   new Xor3Gate(scanner); break;
	  case XNOR3GATE:  new Xnor3Gate(scanner); break;
	  case NAND3GATE:  new Nand3Gate(scanner); break;
	  case NOR3GATE:   new Nor3Gate(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 SPDTSwitches and call compute on them until
  // no output Wire values are changed or until 1000 iterations (to catch
  // any unstable feedback loops in the circuit)
  private static void computeLoop() {
    int reps = 1000;
    while(changed && --reps!=0) {
      changed = false;
      for( SPDTSwitch spstswitch : allSwitches ) {
	spstswitch.compute();
      }
      for( Gate gate : allGates ) {
	gate.compute();
      }
    }
    if (reps == 0) {
      System.err.println("Unstable circuit: more than 1000 compute loops");
      System.exit(2);
    }
  }

  // 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);
    }
  }
}

class SPDTSwitch extends CircuitElement {
  public String name; // the name of this switch
  private Wire common; // the Wire connected to single pole of this switch
  private Wire connect0; // the Wire connected to one throw of this switch
  private Wire connect1; // the Wire connected to the other throw of this switch
  private boolean connect; // false if common is connect to connect0, true if
                           // common is connected to connect1
  SPDTSwitch(Scanner scanner) {
    Circuit.allSwitches.add(this); // add this SPDTSwitch to allSwitches

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

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

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

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

  protected void compute() {
    common.set_value(connect ? connect1.get_value() : connect0.get_value());
  }

  public void askConnect(BufferedReader stdin) throws IOException {
    System.out.print("Please enter the switch position for SPDTSwitch "+name+" (0 or 1): ");
    connect = stdin.readLine().equals("1");
  }
}

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));
  }
}

/** Gates with exactly three inputs  */
abstract class TernaryGate extends Gate {
  protected Wire input1; // the Wire connected to the first input of the Gate
  protected Wire input2; // the Wire connected to the second input of the Gate
  protected Wire input3; // the Wire connected to the third input of the Gate

  TernaryGate(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 input3
    scanner.findInLine("input3 (\\w+)");
    result = scanner.match();
    input3 = 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());
    }
}

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

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

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

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

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

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

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

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

class And3Gate extends TernaryGate {
  And3Gate(Scanner scanner) {
    super(scanner);
  }

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

class Or3Gate extends TernaryGate {
  Or3Gate(Scanner scanner) {
    super(scanner);
  }

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

class Nand3Gate extends TernaryGate {
  Nand3Gate(Scanner scanner) {
    super(scanner);
  }

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

class Nor3Gate extends TernaryGate {
  Nor3Gate(Scanner scanner) {
    super(scanner);
  }

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

class Xor3Gate extends TernaryGate {
  Xor3Gate(Scanner scanner) {
    super(scanner);
  }

  protected void compute() {
    output.set_value(! ((!(input1.get_value() == input2.get_value())) ==
			input3.get_value()));
    }
}

class Xnor3Gate extends TernaryGate {
  Xnor3Gate(Scanner scanner) {
    super(scanner);
  }

  protected void compute() {
    output.set_value( (!(input1.get_value() == input2.get_value())) ==
                      input3.get_value());
    }
}
