using System.Collections.Concurrent;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms;
namespace ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms.Tests;
/// Test fake implementation of for verifying subscription behavior.
public sealed class FakeUpstream : ITagUpstreamSource
{
private readonly ConcurrentDictionary _values = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary>> _subs
= new(StringComparer.Ordinal);
/// Gets the current count of active subscriptions.
public int ActiveSubscriptionCount { get; private set; }
/// Sets a tag value without notifying subscribers.
/// The tag path to set.
/// The value to set for the tag.
/// The OPC UA status code for the value.
public void Set(string path, object? value, uint statusCode = 0u)
{
var now = DateTime.UtcNow;
_values[path] = new DataValueSnapshot(value, statusCode, now, now);
}
/// Sets a tag value and notifies all current subscribers.
/// The tag path to set.
/// The value to set for the tag.
/// The OPC UA status code for the value.
public void Push(string path, object? value, uint statusCode = 0u)
{
Set(path, value, statusCode);
if (_subs.TryGetValue(path, out var list))
{
Action[] snap;
lock (list) { snap = list.ToArray(); }
foreach (var obs in snap) obs(path, _values[path]);
}
}
/// Reads the current value of a tag, or returns a bad-status snapshot if not set.
/// The tag path to read.
public DataValueSnapshot ReadTag(string path)
=> _values.TryGetValue(path, out var v) ? v
: new DataValueSnapshot(null, 0x80340000u, null, DateTime.UtcNow);
/// Subscribes an observer to tag changes for the given path.
/// The tag path to subscribe to.
/// The observer callback to invoke on tag changes.
public IDisposable SubscribeTag(string path, Action observer)
{
var list = _subs.GetOrAdd(path, _ => []);
lock (list) { list.Add(observer); }
ActiveSubscriptionCount++;
return new Unsub(this, path, observer);
}
/// Disposable subscription handle that unsubscribes the observer when disposed.
private sealed class Unsub : IDisposable
{
private readonly FakeUpstream _up;
private readonly string _path;
private readonly Action _observer;
/// Initializes the unsubscription handle with references needed to clean up the subscription.
/// The upstream source containing the subscription list.
/// The tag path to unsubscribe from.
/// The observer to remove from the subscription list.
public Unsub(FakeUpstream up, string path, Action observer)
{ _up = up; _path = path; _observer = observer; }
/// Removes the observer from the subscription list.
public void Dispose()
{
if (_up._subs.TryGetValue(_path, out var list))
{
lock (list)
{
if (list.Remove(_observer)) _up.ActiveSubscriptionCount--;
}
}
}
}
}