using RobotNet10.CustomConfiguration.Models; using RobotNet10.CustomConfiguration.Services; namespace RobotNet10.FleetManager.Services.ConfigManager; /// /// Service implementation for managing ACSTraffic configurations /// public class ACSTrafficConfig : IACSTrafficConfig { private readonly IConfigManager _configManager; private readonly Logger _logger; private readonly Lock _lockObject = new(); private bool _configLoaded = false; /// /// Event triggered when ACSTraffic configuration is changed/reloaded /// public event EventHandler? ConfigChanged; private bool _trafficEnable = false; private int _trafficInterval = 1000; private string _trafficURL = string.Empty; private Dictionary _acsZoneMaping = []; private Dictionary _acsOutMaping = []; private bool _publishEnable = false; private string _publishURL = string.Empty; private int _publishInterval = 1000; private const string ACS_TRAFFIC_CONFIG_TYPE = "ACSTrafficConfig"; public ACSTrafficConfig(IConfigManager configManager, Logger logger) { _configManager = configManager ?? throw new ArgumentNullException(nameof(configManager)); _logger = logger; // Subscribe to config changes _configManager.ConfigChanged += OnConfigChanged; } public bool TrafficEnable { get { EnsureConfigLoaded(); return _trafficEnable; } } public int TrafficInterval { get { EnsureConfigLoaded(); return _trafficInterval; } } public string TrafficURL { get { EnsureConfigLoaded(); return _trafficURL; } } public Dictionary ACSZoneMaping { get { EnsureConfigLoaded(); return _acsZoneMaping; } } public bool PublishEnable { get { EnsureConfigLoaded(); return _publishEnable; } } public string PublishURL { get { EnsureConfigLoaded(); return _publishURL; } } public int PublishInterval { get { EnsureConfigLoaded(); return _publishInterval; } } public Dictionary ACSOutMaping { get { EnsureConfigLoaded(); return _acsOutMaping; } } private void EnsureConfigLoaded() { if (!_configLoaded) { lock (_lockObject) { if (!_configLoaded) { LoadACSTrafficConfigAsync().GetAwaiter().GetResult(); } } } } private async Task LoadACSTrafficConfigAsync() { try { var configFile = await _configManager.GetConfigByTypeAsync(ACS_TRAFFIC_CONFIG_TYPE); if (configFile == null) { _logger.Warning("ACSTraffic configuration not found, using defaults"); _trafficEnable = false; _trafficInterval = 1000; _trafficURL = string.Empty; _acsZoneMaping = []; _acsOutMaping = []; _publishEnable = false; _publishURL = string.Empty; _publishInterval = 1000; } else { MapConfigVariablesToProperties(configFile.Variables); _logger.Info("ACSTraffic configuration loaded successfully"); } } catch (Exception ex) { _logger.Error($"Error loading ACSTraffic configuration, using defaults: {ex.Message}"); _trafficEnable = false; _trafficInterval = 1000; _trafficURL = string.Empty; _acsZoneMaping = []; _acsOutMaping = []; _publishEnable = false; _publishURL = string.Empty; _publishInterval = 1000; } finally { _configLoaded = true; // Trigger ConfigChanged event after config is loaded/reloaded OnConfigChanged(); } } private void OnConfigChanged(object? sender, RobotNet10.CustomConfiguration.Events.ConfigChangedEventArgs e) { // Reload config if our config type changed if (e.ConfigType == ACS_TRAFFIC_CONFIG_TYPE) { lock (_lockObject) { _configLoaded = false; } _logger.Info($"ACSTraffic configuration changed ({e.ConfigType}), will reload on next access"); } } /// /// Trigger ConfigChanged event to notify subscribers that config has been reloaded /// private void OnConfigChanged() { ConfigChanged?.Invoke(this, EventArgs.Empty); } private void MapConfigVariablesToProperties(List variables) { foreach (var variable in variables) { if (variable.Value == null) continue; try { switch (variable.Name) { case nameof(TrafficEnable): _trafficEnable = (bool)ConvertValue(variable.Value, typeof(bool))!; break; case nameof(TrafficInterval): _trafficInterval = (int)ConvertValue(variable.Value, typeof(int))!; break; case nameof(TrafficURL): _trafficURL = (string)ConvertValue(variable.Value, typeof(string))!; break; case nameof(ACSZoneMaping): _acsZoneMaping = (Dictionary)ConvertValue(variable.Value, typeof(Dictionary))!; break; case nameof(ACSOutMaping): _acsOutMaping = (Dictionary)ConvertValue(variable.Value, typeof(Dictionary))!; break; case nameof(PublishEnable): _publishEnable = (bool)ConvertValue(variable.Value, typeof(bool))!; break; case nameof(PublishURL): _publishURL = (string)ConvertValue(variable.Value, typeof(string))!; break; case nameof(PublishInterval): _publishInterval = (int)ConvertValue(variable.Value, typeof(int))!; break; default: _logger.Warning($"Unknown variable name: {variable.Name}"); break; } } catch (Exception ex) { _logger.Warning($"Failed to set property {variable.Name} from variable: {ex.Message}"); } } } private object? ConvertValue(object? value, Type targetType) { if (value == null) return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; // If value is already of the correct type, return it if (targetType.IsInstanceOfType(value)) return value; try { // Handle nullable types var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; // Convert based on target type if (underlyingType == typeof(string)) { return value.ToString() ?? string.Empty; } else if (underlyingType == typeof(int)) { if (value is int i) return i; if (int.TryParse(value.ToString(), out var parsed)) return parsed; if (value is double d) return (int)d; throw new InvalidCastException($"Cannot convert {value.GetType()} to int"); } else if (underlyingType == typeof(double)) { if (value is double d) return d; if (double.TryParse(value.ToString(), out var parsed)) return parsed; if (value is int i) return i; throw new InvalidCastException($"Cannot convert {value.GetType()} to double"); } else if (underlyingType == typeof(bool)) { if (value is bool b) return b; if (bool.TryParse(value.ToString(), out var parsed)) return parsed; // Handle numeric values: 0/1, "0"/"1", etc. if (value.ToString()?.Trim() == "0" || value.ToString()?.Trim().ToLower() == "false") return false; if (value.ToString()?.Trim() == "1" || value.ToString()?.Trim().ToLower() == "true") return true; throw new InvalidCastException($"Cannot convert {value.GetType()} to bool"); } else if (underlyingType.IsEnum) { var enumValue = Enum.Parse(underlyingType, value.ToString() ?? "", true); return enumValue; } else if (underlyingType.IsGenericType && underlyingType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { // Handle Dictionary types - try to parse from JSON string return ConvertDictionary(value, underlyingType); } else { // Try standard conversion return Convert.ChangeType(value, underlyingType); } } catch (Exception ex) { _logger.Warning($"Error converting value {value} of type {value.GetType()} to {targetType}: {ex.Message}"); throw; } } /// /// Convert value to Dictionary type (supports Dictionary<string, string>) /// private object ConvertDictionary(object value, Type dictionaryType) { try { // Get key and value types var genericArgs = dictionaryType.GetGenericArguments(); var keyType = genericArgs[0]; var valueType = genericArgs[1]; // If value is already the correct Dictionary type, return it if (dictionaryType.IsInstanceOfType(value)) { return value; } // If value is Dictionary, try to convert if (value is Dictionary stringDict) { var result = Activator.CreateInstance(dictionaryType); var addMethod = dictionaryType.GetMethod("Add"); if (addMethod != null && result != null) { foreach (var kvp in stringDict) { var key = ConvertValue(kvp.Key, keyType); var val = ConvertValue(kvp.Value, valueType); addMethod.Invoke(result, [key, val]); } return result; } } // Try parse as JSON string var jsonString = value.ToString(); if (!string.IsNullOrEmpty(jsonString)) { try { var doc = System.Text.Json.JsonDocument.Parse(jsonString); if (doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Object) { var result = Activator.CreateInstance(dictionaryType); var addMethod = dictionaryType.GetMethod("Add"); if (addMethod != null && result != null) { foreach (var prop in doc.RootElement.EnumerateObject()) { // Convert key var key = ConvertValue(prop.Name, keyType); // Convert value var val = ConvertValue(prop.Value.GetString() ?? prop.Value.GetRawText(), valueType); addMethod.Invoke(result, [key, val]); } return result; } } } catch (Exception ex) { _logger.Warning($"Failed to parse Dictionary from JSON string: {ex.Message}"); } } // If all else fails, return default (empty dictionary) _logger.Warning($"Cannot convert {value.GetType()} to {dictionaryType}, using default (empty dictionary)"); return Activator.CreateInstance(dictionaryType) ?? throw new InvalidOperationException($"Cannot create instance of {dictionaryType}"); } catch (Exception ex) { _logger.Warning($"Error converting Dictionary: {ex.Message}"); // Return default (empty dictionary) return Activator.CreateInstance(dictionaryType) ?? throw new InvalidOperationException($"Cannot create instance of {dictionaryType}"); } } }