using System;
using System.Collections.Generic;
using System.Globalization;
namespace Uigenx.Actions
{
///
/// Optional key/value bag carried by an action. The default value holds no dictionary, so an
/// action without a payload costs nothing to create, copy or read.
///
public readonly struct UgxActionPayload : IEquatable
{
static readonly Dictionary EmptyMap = new Dictionary(0);
readonly Dictionary _values;
public UgxActionPayload(Dictionary values)
{
_values = values;
}
public static UgxActionPayload Empty => default;
public bool IsEmpty => _values == null || _values.Count == 0;
public int Count => _values?.Count ?? 0;
/// Never null. Callers must not mutate the result.
public IReadOnlyDictionary Values => _values ?? EmptyMap;
public Dictionary.Enumerator GetEnumerator() => (_values ?? EmptyMap).GetEnumerator();
public bool TryGet(string key, out object value)
{
if (_values == null || key == null)
{
value = null;
return false;
}
return _values.TryGetValue(key, out value);
}
public bool Has(string key) => _values != null && key != null && _values.ContainsKey(key);
public int GetInt(string key, int fallback = 0)
=> TryGet(key, out var raw) && TryToDouble(raw, out var d) ? (int)Math.Round(d) : fallback;
public long GetLong(string key, long fallback = 0L)
=> TryGet(key, out var raw) && TryToDouble(raw, out var d) ? (long)Math.Round(d) : fallback;
public float GetFloat(string key, float fallback = 0f)
=> TryGet(key, out var raw) && TryToDouble(raw, out var d) ? (float)d : fallback;
public bool GetBool(string key, bool fallback = false)
{
if (!TryGet(key, out var raw)) return fallback;
switch (raw)
{
case bool b: return b;
case string s:
if (bool.TryParse(s, out var parsed)) return parsed;
return TryToDouble(s, out var n) ? n != 0d : fallback;
default:
return TryToDouble(raw, out var d) ? d != 0d : fallback;
}
}
public string GetString(string key, string fallback = null)
{
if (!TryGet(key, out var raw) || raw == null) return fallback;
if (raw is string s) return s;
if (raw is IFormattable f) return f.ToString(null, CultureInfo.InvariantCulture);
return raw.ToString();
}
/// Returns a copy with one more entry. Allocates; use it off hot paths.
public UgxActionPayload With(string key, object value)
{
var copy = _values == null
? new Dictionary(1)
: new Dictionary(_values);
copy[key] = value;
return new UgxActionPayload(copy);
}
public static UgxActionPayload Of(string key, object value)
=> new UgxActionPayload(new Dictionary(1) { [key] = value });
public static UgxActionPayload Of(string k0, object v0, string k1, object v1)
=> new UgxActionPayload(new Dictionary(2) { [k0] = v0, [k1] = v1 });
static bool TryToDouble(object raw, out double value)
{
switch (raw)
{
case null: value = 0d; return false;
case double d: value = d; return true;
case float f: value = f; return true;
case int i: value = i; return true;
case long l: value = l; return true;
case short sh: value = sh; return true;
case byte by: value = by; return true;
case decimal m: value = (double)m; return true;
case bool b: value = b ? 1d : 0d; return true;
case string s: return double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out value);
default: value = 0d; return false;
}
}
public bool Equals(UgxActionPayload other) => ReferenceEquals(_values, other._values);
public override bool Equals(object obj) => obj is UgxActionPayload other && Equals(other);
public override int GetHashCode() => _values?.GetHashCode() ?? 0;
}
}