Code/Core/Reference.cs
namespace Nodebox;


public class Reference : IEquatable<Reference> {
    public Reference() {
        Target = null;
        PropertyName = null;
    }

    public Reference(object target, PropertyDescription propertyDescription) {
        Target = target;
        PropertyName = propertyDescription.Name;
    }

    public Reference(object target, string propertyName) {
        Target = target;
        PropertyName = propertyName;
    }

    [JsonIgnore]
    private object _target;
    [JsonIgnore]
    private string _propertyName;
    [JsonIgnore]
    private PropertyDescription _propertyDescription;

    [Property]
    [JsonInclude]
    public object Target {
        get => _target; set {
            _target = value;
            UpdatePropertyDescription();
        }
    }

    [Property]
    [JsonInclude]
    public string PropertyName {
        get => _propertyName; set {
            _propertyName = value;
            UpdatePropertyDescription();
        }
    }

    [JsonIgnore]
    public PropertyDescription PropertyDescription => _propertyDescription;
    [JsonIgnore]
    public Type PropertyType => PropertyDescription.PropertyType;


    [JsonIgnore]
    public bool CanWrite => PropertyDescription.CanWrite;
    [JsonIgnore]
    public bool CanRead => PropertyDescription.CanRead;

    public bool Write(object value) => Write<object>(value);
    public bool Write<T>(T value) {
        if (!IsValid()) {
            return false;
        }

        if (!CanWrite) {
            return false;
        }

        PropertyDescription.SetValue(Target, value);
        return true;
    }

    public bool TryRead(out object value) => TryRead<object>(out value);
    public bool TryRead<T>(out T value) {
        value = default;
        if (!IsValid()) {
            return false;
        }

        if (!CanRead) {
            return false;
        }

        value = Read<T>();
        return true;
    }

    public object Read() => PropertyDescription.GetValue(Target);
    public T Read<T>() => (T)PropertyDescription.GetValue(Target);

    public virtual bool IsValid() {
        return Target != null &&
            PropertyDescription != null &&
            PropertyDescription.TypeDescription.TargetType == Target.GetType();
    }

    private void UpdatePropertyDescription() {
        _propertyDescription = TypeLibrary.GetType(_target.GetType()).GetProperty(_propertyName);
    }

    public override bool Equals(object obj) {
        if (obj is Reference reference) {
            return Equals(reference);
        }

        return false;
    }

    public override int GetHashCode() {
        return HashCode.Combine(Target, PropertyDescription);
    }

    public override string ToString() => $"{PropertyDescription?.Name} on {Target?.ToString() ?? "null"}";

    public bool Equals(Reference other) => Target == other.Target && PropertyDescription.Name == other.PropertyDescription.Name;
}

public class Reference<T> : Reference, IEquatable<Reference<T>> {
    public Reference() {
        Target = null;
        PropertyName = null;
    }

    public Reference(object target, PropertyDescription propertyDescription) : base(target, propertyDescription) { }
    public Reference(object target, string propertyName) : base(target, propertyName) { }

    [JsonInclude]
    public Type Type { get; private set; } = typeof(T);

    public override bool IsValid() {
        return base.IsValid() && Type == PropertyType;
    }

    public bool Write(T value) => Write<T>(value);

    public bool TryRead(out T value) => TryRead<T>(out value);

    public new T Read() => Read<T>();

    public bool Equals(Reference<T> other) => Target == other.Target && PropertyDescription.Name == other.PropertyDescription.Name;
}