using System.Reflection; using RobotNet10.CustomConfiguration.Events; using RobotNet10.CustomConfiguration.Models; using RobotNet10.CustomConfiguration.Services; using RobotNet10.FleetManager.Services.TrafficControl.Models; namespace RobotNet10.FleetManager.Services.ConfigManager; /// /// Service implementation for managing TrafficControl configurations /// public class TrafficConfig : ITrafficConfig { private readonly IConfigManager _configManager; private readonly Logger _logger; private readonly Lock _lockObject = new(); private TrafficControlConfig? _trafficControlConfig; private bool _configLoaded = false; private const string CONFLICT_DETECTION_CONFIG_TYPE = "TrafficConflictDetectionConfig"; private const string BASE_HORIZON_CONFIG_TYPE = "TrafficBaseHorizonConfig"; private const string CONFLICT_RESOLUTION_CONFIG_TYPE = "TrafficConflictResolutionConfig"; private const string PRIORITY_CONFIG_TYPE = "TrafficPriorityConfig"; private const string PATH_PLANNING_CONFIG_TYPE = "TrafficPathPlanningConfig"; public TrafficConfig(IConfigManager configManager, Logger logger) { _configManager = configManager ?? throw new ArgumentNullException(nameof(configManager)); _logger = logger; // Subscribe to config changes _configManager.ConfigChanged += OnConfigChanged; } public TrafficControlConfig GetTrafficControlConfig() { if (!_configLoaded) { lock (_lockObject) { if (!_configLoaded) { LoadTrafficControlConfigAsync().GetAwaiter().GetResult(); } } } return _trafficControlConfig ?? throw new InvalidOperationException("TrafficControl configuration not loaded"); } private async Task LoadTrafficControlConfigAsync() { try { // Load all nested configs var conflictDetectionConfig = await LoadNestedConfigAsync(CONFLICT_DETECTION_CONFIG_TYPE); var baseHorizonConfig = await LoadNestedConfigAsync(BASE_HORIZON_CONFIG_TYPE); var conflictResolutionConfig = await LoadNestedConfigAsync(CONFLICT_RESOLUTION_CONFIG_TYPE); var priorityConfig = await LoadNestedConfigAsync(PRIORITY_CONFIG_TYPE); var pathPlanningConfig = await LoadNestedConfigAsync(PATH_PLANNING_CONFIG_TYPE); // Combine into TrafficControlConfig _trafficControlConfig = new TrafficControlConfig { ConflictDetection = conflictDetectionConfig, BaseHorizon = baseHorizonConfig, ConflictResolution = conflictResolutionConfig, Priority = priorityConfig, PathPlanning = pathPlanningConfig }; _configLoaded = true; _logger.Info("TrafficControl configuration loaded successfully"); } catch (Exception ex) { _logger.Error($"Error loading TrafficControl configuration, using defaults: {ex.Message}"); _trafficControlConfig = new TrafficControlConfig(); _configLoaded = true; } } private async Task LoadNestedConfigAsync(string configType) where T : new() { try { var configFile = await _configManager.GetConfigByTypeAsync(configType); if (configFile == null) { _logger.Warning($"{configType} configuration not found, using defaults"); return new T(); } var config = MapConfigVariablesToObject(configFile.Variables); _logger.Info($"{configType} configuration loaded successfully"); return config; } catch (Exception ex) { _logger.Warning($"Error loading {configType} configuration, using defaults: {ex.Message}"); return new T(); } } private void OnConfigChanged(object? sender, ConfigChangedEventArgs e) { // Reload config if any of our config types changed if (e.ConfigType == CONFLICT_DETECTION_CONFIG_TYPE || e.ConfigType == BASE_HORIZON_CONFIG_TYPE || e.ConfigType == CONFLICT_RESOLUTION_CONFIG_TYPE || e.ConfigType == PRIORITY_CONFIG_TYPE || e.ConfigType == PATH_PLANNING_CONFIG_TYPE) { lock (_lockObject) { _configLoaded = false; _trafficControlConfig = null; } _logger.Info($"TrafficControl configuration changed ({e.ConfigType}), will reload on next access"); } } private T MapConfigVariablesToObject(List variables) where T : new() { var obj = new T(); var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); foreach (var property in properties) { // Find variable by exact name match (case-sensitive) var variable = variables.FirstOrDefault(v => v.Name == property.Name); if (variable != null && variable.Value != null) { try { var convertedValue = ConvertValue(variable.Value, property.PropertyType); property.SetValue(obj, convertedValue); } catch (Exception ex) { _logger.Warning($"Failed to set property {property.Name} from variable {variable.Name}: {ex.Message}"); } } } return obj; } 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<NavigationType, PathPlanningMethod>) /// 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 (e.g., "Differential" -> NavigationType.Differential) var key = ConvertEnumKey(prop.Name, keyType); // Convert value (e.g., "Basic" -> PathPlanningMethod.Basic) var val = ConvertEnumValue(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}"); } } /// /// Convert string to enum key (e.g., "Differential" -> NavigationType.Differential) /// private static object ConvertEnumKey(string keyString, Type enumType) { if (enumType.IsEnum) { if (Enum.TryParse(enumType, keyString, true, out var enumValue)) { return enumValue; } } // If not enum, try standard conversion return Convert.ChangeType(keyString, enumType); } /// /// Convert string to enum value (e.g., "Basic" -> PathPlanningMethod.Basic) /// private static object ConvertEnumValue(string valueString, Type enumType) { if (enumType.IsEnum) { if (Enum.TryParse(enumType, valueString, true, out var enumValue)) { return enumValue; } } // If not enum, try standard conversion return Convert.ChangeType(valueString, enumType); } }