using System.Globalization;
using S7.Net.Protocol;
namespace ZB.MOM.WW.OtOpcUa.Driver.S7;
///
/// ISO-TSAP helpers for the optional LocalTsap / RemoteTsap / ConnectionType
/// config: parsing the hex text form and resolving the effective handed to
/// S7.Net. Pure — no I/O — so the resolution rules are unit-testable without a PLC.
///
internal static class S7Tsap
{
///
/// Parses a TSAP written as two hex bytes — "01.00", "03:01" (1–2 digits per byte) — or
/// as one 16-bit hex value, "0100" / "0x0301". High byte first.
///
/// The configured text.
/// The 16-bit TSAP (first << 8 | second).
/// True when is a valid TSAP.
internal static bool TryParse(string? text, out ushort tsap)
{
tsap = 0;
if (string.IsNullOrWhiteSpace(text)) return false;
var t = text.Trim();
var sep = t.IndexOfAny(['.', ':', '-']);
if (sep >= 0)
{
if (!TryHexByte(t[..sep], out var hi) || !TryHexByte(t[(sep + 1)..], out var lo)) return false;
tsap = (ushort)((hi << 8) | lo);
return true;
}
if (t.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) t = t[2..];
if (t.Length is < 1 or > 4) return false;
return ushort.TryParse(t, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out tsap);
}
private static bool TryHexByte(string s, out byte b)
{
b = 0;
s = s.Trim();
return s.Length is >= 1 and <= 2
&& byte.TryParse(s, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out b);
}
/// Formats a 16-bit TSAP as canonical "XX.YY" (upper-case hex).
/// The 16-bit TSAP.
/// The canonical text.
internal static string Format(ushort tsap) => $"{tsap >> 8:X2}.{tsap & 0xFF:X2}";
///
/// Resolves the TSAP pair to connect with, or null when the options carry no override (the
/// caller then keeps the plain Plc(cpu, host, port, rack, slot) construction, unchanged).
/// Starts from S7.Net's own per-CPU default for 's rack/slot, then applies,
/// in order: LocalTsap replaces the local TSAP; RemoteTsap replaces the remote TSAP;
/// ConnectionType replaces only the remote TSAP's first byte.
///
/// The driver options.
/// The effective pair, or null for "no override".
internal static TsapPair? Resolve(S7DriverOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var hasLocal = !string.IsNullOrWhiteSpace(options.LocalTsap);
var hasRemote = !string.IsNullOrWhiteSpace(options.RemoteTsap);
if (!hasLocal && !hasRemote && options.ConnectionType is null) return null;
var def = TsapPair.GetDefaultTsapPair(S7CpuTypeMap.ToS7Net(options.CpuType), options.Rack, options.Slot);
var local = def.Local;
var remote = def.Remote;
if (hasLocal)
{
if (!TryParse(options.LocalTsap, out var l))
throw new FormatException($"S7 LocalTsap '{options.LocalTsap}' is not a valid TSAP (expected e.g. \"01.00\").");
local = new Tsap((byte)(l >> 8), (byte)(l & 0xFF));
}
if (hasRemote)
{
if (!TryParse(options.RemoteTsap, out var r))
throw new FormatException($"S7 RemoteTsap '{options.RemoteTsap}' is not a valid TSAP (expected e.g. \"03.01\").");
remote = new Tsap((byte)(r >> 8), (byte)(r & 0xFF));
}
else if (options.ConnectionType is { } ct)
{
remote = new Tsap((byte)ct, remote.SecondByte);
}
return new TsapPair(local, remote);
}
}