namespace StoneRed.LogicSimulator.Simulation;
///
/// Defines a reusable circuit component that can be registered as a macro gate.
/// Circuit definitions specify gates, connections, input pins, output pins, and nested macro instances.
///
public sealed class CircuitDefinition
{
private readonly List gateKinds = [];
private readonly List<(int FromGate, int ToGate, byte ToInputBit)> connections = [];
private readonly List inputPins = [];
private readonly List outputPins = [];
private readonly List macroInstances = [];
///
/// Gets the read-only list of gate types in this circuit definition.
///
public IReadOnlyList GateKinds => gateKinds;
///
/// Gets the read-only list of connections between gates.
///
public IReadOnlyList<(int FromGate, int ToGate, byte ToInputBit)> Connections => connections;
///
/// Gets the read-only list of gate IDs designated as input pins.
///
public IReadOnlyList InputPins => inputPins;
///
/// Gets the read-only list of gate IDs designated as output pins.
///
public IReadOnlyList OutputPins => outputPins;
///
/// Gets the read-only list of nested macro instances within this definition.
///
public IReadOnlyList MacroInstances => macroInstances;
///
/// Represents a nested macro gate instance within a circuit definition.
///
/// The name of the macro gate to instantiate.
/// Array of gate IDs representing the input connections.
/// Array of gate IDs representing the output connections.
public sealed record MacroInstanceDef(string Name, int[] Inputs, int[] Outputs);
///
/// Adds a logic gate to the circuit definition.
///
/// The type of gate to add.
/// The gate ID assigned to the newly created gate.
public int AddGate(GateKind kind)
{
int id = gateKinds.Count;
gateKinds.Add(kind);
return id;
}
///
/// Adds a Source gate and marks it as an input pin of this circuit.
///
/// The gate ID of the newly created input pin.
public int AddInputPin()
{
int id = AddGate(GateKind.Source);
inputPins.Add(id);
return id;
}
///
/// Adds a Sink gate and marks it as an output pin of this circuit.
///
/// The gate ID of the newly created output pin.
public int AddOutputPin()
{
int id = AddGate(GateKind.Sink);
outputPins.Add(id);
return id;
}
///
/// Adds a nested macro gate instance to the circuit definition.
/// Creates Buffer gates for inputs and Sink gates for outputs.
///
/// The name of the macro gate to instantiate.
/// The number of input pins the macro requires.
/// The number of output pins the macro provides.
/// A containing the gate IDs for the instance's pins.
/// Thrown when name is null or whitespace.
/// Thrown when inputCount or outputCount is negative.
public MacroInstanceDef AddMacroInstance(string name, int inputCount, int outputCount)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Macro name must not be empty.", nameof(name));
}
ArgumentOutOfRangeException.ThrowIfNegative(inputCount);
ArgumentOutOfRangeException.ThrowIfNegative(outputCount);
int[] inputs = new int[inputCount];
for (int i = 0; i < inputCount; i++)
{
inputs[i] = AddGate(GateKind.Buffer);
}
int[] outputs = new int[outputCount];
for (int i = 0; i < outputCount; i++)
{
outputs[i] = AddGate(GateKind.Sink);
}
MacroInstanceDef instance = new MacroInstanceDef(name, inputs, outputs);
macroInstances.Add(instance);
return instance;
}
///
/// Connects the output of one gate to the input of another gate.
///
/// The gate ID whose output will be connected.
/// The gate ID that will receive the signal.
/// The input bit position (0-31) on the destination gate.
/// Thrown when gate IDs are invalid or toInputBit is not between 0 and 31.
public void Connect(int fromGate, int toGate, int toInputBit)
{
if ((uint)fromGate >= (uint)gateKinds.Count)
{
throw new ArgumentOutOfRangeException(nameof(fromGate));
}
if ((uint)toGate >= (uint)gateKinds.Count)
{
throw new ArgumentOutOfRangeException(nameof(toGate));
}
if ((uint)toInputBit >= 32u)
{
throw new ArgumentOutOfRangeException(nameof(toInputBit));
}
connections.Add((fromGate, toGate, (byte)toInputBit));
}
///
/// Validates the circuit definition for correctness.
/// Ensures proper gate types, valid connections, and no structural errors.
///
/// Thrown when the circuit definition contains invalid structure or gate usage.
public void Validate()
{
bool[] hasIncoming = new bool[gateKinds.Count];
for (int i = 0; i < connections.Count; i++)
{
(_, int to, _) = connections[i];
hasIncoming[to] = true;
}
HashSet inputPinSet = [.. inputPins];
for (int i = 0; i < gateKinds.Count; i++)
{
if (gateKinds[i] == GateKind.Source && !inputPinSet.Contains(i))
{
throw new InvalidOperationException("Only Source gates marked as InputPins are allowed inside a circuit definition.");
}
if (gateKinds[i] == GateKind.Lut)
{
throw new InvalidOperationException("Circuit definitions must not contain LUT gates.");
}
}
HashSet macroPinGates = [];
foreach (MacroInstanceDef instance in macroInstances)
{
if (string.IsNullOrWhiteSpace(instance.Name))
{
throw new InvalidOperationException("Macro instance name must not be empty.");
}
foreach (int pin in instance.Inputs)
{
if ((uint)pin >= (uint)gateKinds.Count)
{
throw new InvalidOperationException("Macro instance input pin is out of range.");
}
if (gateKinds[pin] != GateKind.Buffer)
{
throw new InvalidOperationException("Macro instance inputs must be Buffer gates.");
}
if (!macroPinGates.Add(pin))
{
throw new InvalidOperationException("Macro instance pin is used more than once.");
}
}
foreach (int pin in instance.Outputs)
{
if ((uint)pin >= (uint)gateKinds.Count)
{
throw new InvalidOperationException("Macro instance output pin is out of range.");
}
if (gateKinds[pin] != GateKind.Sink)
{
throw new InvalidOperationException("Macro instance outputs must be Sink gates.");
}
if (!macroPinGates.Add(pin))
{
throw new InvalidOperationException("Macro instance pin is used more than once.");
}
}
}
foreach (int pin in macroPinGates)
{
if (inputPinSet.Contains(pin) || outputPins.Contains(pin))
{
throw new InvalidOperationException("Macro instance pins must not be listed as InputPins/OutputPins.");
}
}
foreach (int input in inputPins)
{
if (gateKinds[input] != GateKind.Source)
{
throw new InvalidOperationException("Input pins must be Source gates.");
}
if (hasIncoming[input])
{
throw new InvalidOperationException("Input pins must not have incoming connections.");
}
}
foreach (int output in outputPins)
{
if (gateKinds[output] != GateKind.Sink)
{
throw new InvalidOperationException("Output pins must be Sink gates.");
}
}
if (inputPins.Distinct().Count() != inputPins.Count)
{
throw new InvalidOperationException("Input pins contain duplicates.");
}
if (outputPins.Distinct().Count() != outputPins.Count)
{
throw new InvalidOperationException("Output pins contain duplicates.");
}
}
}