#nullable enable
using System;
using System.Collections.Generic;
namespace IFSEngine.Utility;
/// <summary>
/// Reflection utilities to access nested properties and fields.
/// Based on <a href="https://stackoverflow.com/questions/48082626/set-property-in-nested-object-using-reflection">this thread</a>, heavily modified and simplified.
/// </summary>
internal static class NestedReflectionHelper
{
/// <param name="obj">The root object that the path applies to.</param>
/// <param name="memberPath">A path to a property or field under the root object. Such as "[0].MyDict.[asd]".</param>
/// <param name="newValue">The new value to be set for the specified member.</param>
public static void SetMemberValueByPath(object obj, string memberPath, object newValue)
{
var pathSegments = new Queue<string>(memberPath.Split('.'));
_ = SetRecursive(obj, pathSegments, newValue);
}
private static object SetRecursive(object obj, Queue<string> pathSegments, object newValue)
{
if (pathSegments.TryDequeue(out var segment))
{
ParseMemberPathSegment(segment, out var memberName, out var index);
var propInfo = obj.GetType().GetProperty(memberName);
if (propInfo is not null)
{//member is a property
if (pathSegments.Count > 0)
{
var propValue = propInfo.GetValue(obj, index)!;
propValue = SetRecursive(propValue, pathSegments, newValue);
if (propValue.GetType().IsValueType)
propInfo.SetValue(obj, propValue, index);
}
else
propInfo.SetValue(obj, newValue, index);
}
else
{
var methodInfo = obj.GetType().GetMethod(memberName, index != null
? [index[0]!.GetType()] : Type.EmptyTypes);
if (methodInfo is not null && pathSegments.Count > 0)
{
var methodValue = methodInfo.Invoke(obj, index)!;
methodValue = SetRecursive(methodValue, pathSegments, newValue);
if (methodValue.GetType().IsValueType)
methodInfo.Invoke(obj, [methodValue]);
}
else
{//member is a field
var fieldInfo = obj.GetType().GetField(memberName)!;
if (pathSegments.Count > 0)
{
var fieldValue = fieldInfo.GetValue(obj)!;
fieldValue = SetRecursive(fieldValue, pathSegments, newValue);
if (fieldValue.GetType().IsValueType)
fieldInfo.SetValue(obj, fieldValue);
}
else
fieldInfo.SetValue(obj, Convert.ToSingle(newValue));//hack: cast double to float
}
}
}
return obj;
}
private static void ParseMemberPathSegment(string segment, out string memberName, out object?[]? index)
{
if (segment.Contains('['))
{//indexer segment
memberName = segment.Split('[')[0];
if(string.IsNullOrEmpty(memberName))
memberName = "Item";//support earlier params that use deprecated indexer
var indexString = segment[(segment.IndexOf('[') + 1)..^1];
index = [int.TryParse(indexString, out int numIndex) ? numIndex : indexString];
}
else
{//member name segment
memberName = segment;
index = null;
}
}
}