using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Text.RegularExpressions;
namespace axXez.TS3.Protocol
{
/// A single key-value entry from a TeamSpeak text-protocol message, with typed accessors and deserialization helpers.
public class TsDataEntry
{
private static readonly Regex _kvRegex = new(@"[ ]?(?[^ =\n|]*)=(?[^ \n|]*)", RegexOptions.Compiled);
private readonly Dictionary _data;
/// Returns the value of the first matching key, or an empty string if none is found.
public string this[params string[] keys]
=> GetValue(keys);
internal TsDataEntry(Dictionary data)
=> _data = data;
internal static TsDataEntry Parse(string part)
{
var dict = new Dictionary();
foreach (Match match in _kvRegex.Matches(part))
{
string key = match.Groups["name"].Value;
if (!dict.TryAdd(key, TsEncoding.Unescape(match.Groups["value"].Value)))
GlobalLogger.Log("TsEncoding", $"Duplicate key '{key}' in entry, ignoring.", LogType.Warning);
}
return new TsDataEntry(dict);
}
/// Returns true and sets to the first matching key found in this entry.
public bool ContainsKey(out string key, params string[] keys)
{
key = keys.FirstOrDefault(_data.ContainsKey);
return key != null;
}
/// Returns true and sets to the raw string value of the first matching key, or null if not found.
public bool TryGetValue(out string value, params string[] keys)
{
string key = keys.FirstOrDefault(_data.ContainsKey);
value = key != null ? _data[key] : null;
return key != null;
}
/// Returns the raw string value of the first matching key, or an empty string if not found.
public string GetValue(params string[] keys)
{
string key = keys.FirstOrDefault(_data.ContainsKey);
return key != null ? _data[key] : "";
}
/// Returns the numeric value of the first matching key. Returns -1 for signed types or 0 for unsigned types when the key is not found or the value is unparseable.
public T GetValue(params string[] keys) where T : INumber
{
string value = GetValue(keys);
if (!string.IsNullOrEmpty(value) && T.TryParse(value, null, out T result))
return result;
return T.TryParse("-1", null, out T fallback) ? fallback : T.Zero;
}
internal object GetEntity(Type targetType)
=> Activator.CreateInstance(targetType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, [this], null);
/// Deserializes this entry into a of type .
public T GetEntity() where T : TsEntity
=> (T)Activator.CreateInstance(typeof(T), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, [this], null);
/// Returns the Unix-timestamp field of the first matching key as a local .
public DateTime GetDateTime(params string[] keys)
=> new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)
.AddSeconds(GetValue(keys))
.ToLocalTime();
/// Parses a TeamSpeak boolean string ("0" or "1") into a .
public static bool TryParseBool(string input, out bool value)
{
if (!TryParseNumber(input, out var b))
{
value = false;
return false;
}
value = b == 1;
return true;
}
/// Parses a numeric string into a value of type .
public static bool TryParseNumber(string input, out T value) where T : INumber
{
if (!T.TryParse(input, null, out var n))
{
value = default;
return false;
}
value = n;
return true;
}
/// Parses a millisecond-count string into a boxed as object.
public static bool TryParseTimespan(string input, out object value)
{
if (!TryParseNumber(input, out var ms))
{
value = default;
return false;
}
value = TimeSpan.FromMilliseconds(ms);
return true;
}
/// Parses a Unix-timestamp string into a local boxed as object.
public static bool TryParseDatetime(string input, out object value)
{
if (!TryParseNumber(input, out var seconds))
{
value = default;
return false;
}
value = DateTimeOffset.FromUnixTimeSeconds(seconds).LocalDateTime;
return true;
}
internal Dictionary ToDictionary()
=> _data;
internal void Merge(TsDataEntry incoming)
{
foreach (var kvp in incoming._data)
_data[kvp.Key] = kvp.Value;
}
internal bool Inject(string key, string value)
{
if (!_data.TryGetValue(key, out var current) || !value.Equals(current))
{
_data[key] = value;
return true;
}
return false;
}
}
}