330 lines
13 KiB
C#
330 lines
13 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Service implementation for managing TrafficControl configurations
|
|
/// </summary>
|
|
public class TrafficConfig : ITrafficConfig
|
|
{
|
|
private readonly IConfigManager _configManager;
|
|
private readonly Logger<TrafficConfig> _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<TrafficConfig> 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<ConflictDetectionConfig>(CONFLICT_DETECTION_CONFIG_TYPE);
|
|
var baseHorizonConfig = await LoadNestedConfigAsync<BaseHorizonConfig>(BASE_HORIZON_CONFIG_TYPE);
|
|
var conflictResolutionConfig = await LoadNestedConfigAsync<ConflictResolutionConfig>(CONFLICT_RESOLUTION_CONFIG_TYPE);
|
|
var priorityConfig = await LoadNestedConfigAsync<PriorityConfig>(PRIORITY_CONFIG_TYPE);
|
|
var pathPlanningConfig = await LoadNestedConfigAsync<PathPlanningConfig>(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<T> LoadNestedConfigAsync<T>(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<T>(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<T>(List<ConfigVariable> 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert value to Dictionary type (supports Dictionary<NavigationType, PathPlanningMethod>)
|
|
/// </summary>
|
|
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<string, object>, try to convert
|
|
if (value is Dictionary<string, object> 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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert string to enum key (e.g., "Differential" -> NavigationType.Differential)
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert string to enum value (e.g., "Basic" -> PathPlanningMethod.Basic)
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|