using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using Runtime.ADAggregator; public sealed class ToponControllerOptions { public const string ChannelKey = "topon.channel"; public const string SubChannelKey = "topon.sub_channel"; public const string DebugKey = "topon.debug"; public const string DebuggerKeyKey = "topon.debugger_key"; public const string SDKAreaKey = "topon.sdk_area"; public const string LongitudeKey = "topon.longitude"; public const string LatitudeKey = "topon.latitude"; public const string ExcludeBundleIdsKey = "topon.exclude_bundle_ids"; public const string RewardedExcludeAdSourceIdsKey = "topon.rewarded_exclude_ad_source_ids"; public const string QueryAreaOnInitKey = "topon.query_area_on_init"; public const string InitCustomMapKey = "topon.custom_map"; public const string RewardedCustomDataKey = "topon.rewarded_custom_data"; public string Channel { get; set; } public string SubChannel { get; set; } public bool? Debug { get; set; } public string DebuggerKey { get; set; } public int? SDKArea { get; set; } public double? Longitude { get; set; } public double? Latitude { get; set; } public string[] ExcludeBundleIds { get; set; } public string[] RewardedExcludeAdSourceIds { get; set; } public bool QueryAreaOnInit { get; set; } public Dictionary InitCustomMap { get; set; } public Dictionary RewardedCustomData { get; set; } public Action OnAreaReceived { get; set; } public Action OnAreaError { get; set; } public static ToponControllerOptions Resolve(ADConfig adConfig, object[] args) { var options = new ToponControllerOptions(); options.ApplyCommonKeyValues(adConfig?.CommonKeyValues); options.ApplyLegacyArgs(args); if (args == null) { return options; } foreach (var arg in args) { switch (arg) { case ToponControllerOptions explicitOptions: options.ApplyExplicitOptions(explicitOptions); break; case IDictionary dictionary: options.ApplyDictionary(dictionary); break; } } return options; } private void ApplyCommonKeyValues(List keyValues) { if (keyValues == null || keyValues.Count == 0) { return; } var map = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var keyValue in keyValues) { if (keyValue == null || string.IsNullOrWhiteSpace(keyValue.key)) { continue; } map[keyValue.key] = keyValue.value; } Channel = GetString(map, ChannelKey, "channel") ?? Channel; SubChannel = GetString(map, SubChannelKey, "sub_channel") ?? SubChannel; Debug = GetBool(map, DebugKey, "debug") ?? Debug; DebuggerKey = GetString(map, DebuggerKeyKey) ?? DebuggerKey; SDKArea = GetInt(map, SDKAreaKey) ?? SDKArea; Longitude = GetDouble(map, LongitudeKey) ?? Longitude; Latitude = GetDouble(map, LatitudeKey) ?? Latitude; ExcludeBundleIds = GetStringArray(map, ExcludeBundleIdsKey) ?? ExcludeBundleIds; RewardedExcludeAdSourceIds = GetStringArray(map, RewardedExcludeAdSourceIdsKey) ?? RewardedExcludeAdSourceIds; QueryAreaOnInit = GetBool(map, QueryAreaOnInitKey) ?? QueryAreaOnInit; InitCustomMap = MergeMaps(InitCustomMap, GetPrefixedMap(map, InitCustomMapKey + ".")); RewardedCustomData = MergeMaps(RewardedCustomData, GetPrefixedMap(map, RewardedCustomDataKey + ".")); } private void ApplyLegacyArgs(object[] args) { if (args == null || args.Length == 0) { return; } if (args.Length > 0 && args[0] is string channel) { Channel = channel; } if (args.Length > 1 && TryConvertBool(args[1], out var debug)) { Debug = debug; } } private void ApplyExplicitOptions(ToponControllerOptions explicitOptions) { if (explicitOptions == null) { return; } Channel = explicitOptions.Channel ?? Channel; SubChannel = explicitOptions.SubChannel ?? SubChannel; Debug = explicitOptions.Debug ?? Debug; DebuggerKey = explicitOptions.DebuggerKey ?? DebuggerKey; SDKArea = explicitOptions.SDKArea ?? SDKArea; Longitude = explicitOptions.Longitude ?? Longitude; Latitude = explicitOptions.Latitude ?? Latitude; ExcludeBundleIds = explicitOptions.ExcludeBundleIds ?? ExcludeBundleIds; RewardedExcludeAdSourceIds = explicitOptions.RewardedExcludeAdSourceIds ?? RewardedExcludeAdSourceIds; QueryAreaOnInit = explicitOptions.QueryAreaOnInit || QueryAreaOnInit; InitCustomMap = MergeMaps(InitCustomMap, explicitOptions.InitCustomMap); RewardedCustomData = MergeMaps(RewardedCustomData, explicitOptions.RewardedCustomData); OnAreaReceived = explicitOptions.OnAreaReceived ?? OnAreaReceived; OnAreaError = explicitOptions.OnAreaError ?? OnAreaError; } private void ApplyDictionary(IDictionary dictionary) { if (dictionary == null || dictionary.Count == 0) { return; } var map = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (DictionaryEntry entry in dictionary) { if (entry.Key == null) { continue; } var key = entry.Key.ToString(); if (entry.Value is IDictionary nestedDictionary) { if (string.Equals(key, InitCustomMapKey, StringComparison.OrdinalIgnoreCase)) { InitCustomMap = MergeMaps(InitCustomMap, ToStringMap(nestedDictionary)); continue; } if (string.Equals(key, RewardedCustomDataKey, StringComparison.OrdinalIgnoreCase)) { RewardedCustomData = MergeMaps(RewardedCustomData, ToStringMap(nestedDictionary)); continue; } } map[key] = ConvertDictionaryValue(entry.Value); } Channel = GetString(map, ChannelKey, "channel") ?? Channel; SubChannel = GetString(map, SubChannelKey, "sub_channel") ?? SubChannel; Debug = GetBool(map, DebugKey, "debug") ?? Debug; DebuggerKey = GetString(map, DebuggerKeyKey) ?? DebuggerKey; SDKArea = GetInt(map, SDKAreaKey) ?? SDKArea; Longitude = GetDouble(map, LongitudeKey) ?? Longitude; Latitude = GetDouble(map, LatitudeKey) ?? Latitude; ExcludeBundleIds = GetStringArray(map, ExcludeBundleIdsKey) ?? ExcludeBundleIds; RewardedExcludeAdSourceIds = GetStringArray(map, RewardedExcludeAdSourceIdsKey) ?? RewardedExcludeAdSourceIds; QueryAreaOnInit = GetBool(map, QueryAreaOnInitKey) ?? QueryAreaOnInit; InitCustomMap = MergeMaps(InitCustomMap, GetPrefixedMap(map, InitCustomMapKey + ".")); RewardedCustomData = MergeMaps(RewardedCustomData, GetPrefixedMap(map, RewardedCustomDataKey + ".")); } private static string GetString(IDictionary map, params string[] keys) { foreach (var key in keys) { if (!string.IsNullOrWhiteSpace(key) && map.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value)) { return value.Trim(); } } return null; } private static bool? GetBool(IDictionary map, params string[] keys) { var value = GetString(map, keys); return TryConvertBool(value, out var result) ? result : null; } private static int? GetInt(IDictionary map, params string[] keys) { var value = GetString(map, keys); if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } return null; } private static double? GetDouble(IDictionary map, params string[] keys) { var value = GetString(map, keys); if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var result)) { return result; } return null; } private static string[] GetStringArray(IDictionary map, params string[] keys) { var value = GetString(map, keys); if (string.IsNullOrWhiteSpace(value)) { return null; } var normalized = value.Replace("[", string.Empty) .Replace("]", string.Empty) .Replace("\"", string.Empty) .Replace("'", string.Empty); var items = normalized .Split(new[] { ',', ';', '|', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) .Select(item => item.Trim()) .Where(item => !string.IsNullOrWhiteSpace(item)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); return items.Length > 0 ? items : null; } private static Dictionary GetPrefixedMap(IDictionary map, string prefix) { var result = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var pair in map) { if (!pair.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { continue; } var nestedKey = pair.Key.Substring(prefix.Length).Trim(); if (string.IsNullOrWhiteSpace(nestedKey) || string.IsNullOrWhiteSpace(pair.Value)) { continue; } result[nestedKey] = pair.Value.Trim(); } return result.Count > 0 ? result : null; } private static Dictionary MergeMaps( Dictionary current, Dictionary incoming) { if (incoming == null || incoming.Count == 0) { return current; } var result = current != null ? new Dictionary(current, StringComparer.OrdinalIgnoreCase) : new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var pair in incoming) { if (string.IsNullOrWhiteSpace(pair.Key) || string.IsNullOrWhiteSpace(pair.Value)) { continue; } result[pair.Key] = pair.Value; } return result.Count > 0 ? result : null; } private static bool TryConvertBool(object value, out bool result) { switch (value) { case bool boolValue: result = boolValue; return true; case string stringValue: if (bool.TryParse(stringValue, out result)) { return true; } if (string.Equals(stringValue, "1", StringComparison.OrdinalIgnoreCase)) { result = true; return true; } if (string.Equals(stringValue, "0", StringComparison.OrdinalIgnoreCase)) { result = false; return true; } break; } result = false; return false; } private static string ConvertDictionaryValue(object value) { if (value == null) { return null; } if (value is string stringValue) { return stringValue; } if (value is IEnumerable enumerable) { var items = new List(); foreach (var item in enumerable) { if (item == null) { continue; } items.Add(item.ToString()); } return string.Join(",", items); } return value.ToString(); } private static Dictionary ToStringMap(IDictionary dictionary) { var result = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (DictionaryEntry entry in dictionary) { if (entry.Key == null || entry.Value == null) { continue; } var key = entry.Key.ToString()?.Trim(); var value = entry.Value.ToString()?.Trim(); if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(value)) { continue; } result[key] = value; } return result.Count > 0 ? result : null; } }