Core/Wire.cs
namespace Nodebox;

public class Wire : IDisposable, IMeta
{   
    private readonly WeakReference<Node> _from = new(null);
    public Node From { get => _from.GetTarget(); set => _from.SetTarget(value); }
    public int FromIndex { get; private set; }

    private readonly WeakReference<Node> _to = new(null);
    public Node To { get => _to.GetTarget(); set => _to.SetTarget(value); }
    public int ToIndex { get; private set; }
    
    public NetDictionary<Type, Meta> Meta { get; set; } = new();

    
    public Pin FromPin => From.OutputPins[FromIndex];
    public Pin ToPin => To.InputPins[ToIndex];

    public Type FromType => FromPin.Type;
    public Type ToType => ToPin.Type;

    public Wire(Node from, int fromIndex, Node to, int toIndex, bool unconnected = false) {
		Check(from, fromIndex, to, toIndex);

        From = from;
        FromIndex = fromIndex;
        To = to;
        ToIndex = toIndex;

        if (unconnected) return;
        Connect();
    }

    public void Connect() {
        From.SetOutputWire(FromIndex, this);
        To.SetInputWire(ToIndex, this);

        Pass();
    }

    public static void Check(Node from, int fromIndex, Node to, int toIndex) {
        Assert.NotNull(from, "From can't be null");
        Assert.NotNull(to, "To can't be null");
		
        Assert.False(fromIndex < 0 | fromIndex >= from.OutputPins.Count, "From Index out of bounds");
        Assert.False(toIndex < 0 | toIndex >= to.InputPins.Count, "To Index out of bounds");

        Assert.False(from == to, "Node cannot be connected to itself");
        Assert.False(to.InputWires[toIndex].IsValid(), "Recipient already has a wire connected to this index");
        
        var fromType = from.OutputPins[fromIndex].Type;
        var toType = to.InputPins[toIndex].Type;

        if (fromType == typeof(object) || toType == typeof(object)) return;
        Assert.False(fromType == typeof(Polymorphic) && toType == typeof(Polymorphic), "Both pins can't be Polymorphic");
        if (fromType == typeof(Polymorphic) || toType == typeof(Polymorphic)) return;
        if (Library.TryGetImplicitConversion(fromType, toType, out var _)) return;

        Assert.True(fromType == toType, $"can't connect Pin types ({fromType.GetDisplayName()} -> {toType.GetDisplayName()})");
    }

    
    public T GetMeta<T>() {
        if (!Meta.TryGetValue(typeof(T), out Meta value))
            return default;
        return ((Meta<T>)value).Value;
    }

    public bool TryGetMeta<T>(out T value) {
        value = default;
        var result = Meta.TryGetValue(typeof(T), out Meta meta);
        if (result)
            value = ((Meta<T>)meta).Value;
        return result;
    }

    public void SetMeta<T>(T value) {
        Meta.Add(typeof(T), new Meta<T>(value));
    }

    public void Pass() {
        var value = From.OutputValues[FromIndex];
        
        var inType = From.OutputPins[FromIndex].Type;
        var outType = To.InputPins[ToIndex].Type;

		void done() => To.SetInput( ToIndex, value );

		if (inType == typeof(object) || outType == typeof(object)) {
            done();
            return;
        }

        Assert.False(inType == typeof(Polymorphic) && outType == typeof(Polymorphic), "wtf");
        if (inType == typeof(Polymorphic) || outType == typeof(Polymorphic)) {
            done();
            return;
        }
        
        if (inType == outType) {
            done();
            return;
        }

        Assert.True(Library.TryGetImplicitConversion(inType, outType, out var convert), "wtf");
        value = convert(value);
        To.SetInput(ToIndex, value);
    }
    
	public override string ToString() {
		return $"{From}[{FromIndex}] -> {To}[{ToIndex}]";
	}
    
    private bool disposed = false;
    public void Dispose() {
        if (!disposed) {
            From?.UnsetOutputWire(FromIndex, this);
            To?.UnsetInputWire(ToIndex);

            //Log.Info(("Wire destroyed", From, FromIndex, To, ToIndex));

            disposed = true;
        }

        GC.SuppressFinalize(this);
    }

    ~Wire() {
        Dispose();
    }
}

public static class WireExtensions {
    public static bool IsValid(this WeakReference<Wire> wire) {
        return wire != null && wire.TryGetTarget(out var target) && target != null;
    }
}