using System.Reflection;
namespace IFSRenderer.Shared;
/// <summary>
/// Reads a value out of an <see cref="IFSEngine.Model.IFS"/> by animation path.
/// </summary>
/// <remarks>
/// The engine ships the write half (<c>NestedReflectionHelper.SetMemberValueByPath</c>, used by
/// <c>IFS.EvaluateAt</c>) but it is <c>internal</c> and there is no read half - and keying a
/// parameter needs to capture its <em>current</em> value. This mirrors the engine's segment
/// grammar exactly:
/// <list type="bullet">
/// <item><c>Brightness</c> - a property</item>
/// <item><c>Camera.Position.X</c> - nested property, then a struct field</item>
/// <item><c>Node[123]</c> - a method taking an index (<c>IFS.Node(int)</c>)</item>
/// <item><c>RealParams.[weight]</c> - an empty member name means the default indexer,
/// which is how the app addresses dictionary entries</item>
/// </list>
/// </remarks>
public static class ParamPath
{
public static bool TryGet(object root, string path, out double value)
{
value = 0;
if (root is null || string.IsNullOrWhiteSpace(path))
return false;
object current = root;
foreach (string segment in path.Split('.'))
{
if (current is null)
return false;
if (!TryStep(current, segment, out current))
return false;
}
try
{
value = Convert.ToDouble(current);
return true;
}
catch
{
return false;
}
}
private static bool TryStep(object obj, string segment, out object result)
{
result = null;
ParseSegment(segment, out string memberName, out object[] index);
Type type = obj.GetType();
PropertyInfo property = type.GetProperty(memberName);
if (property is not null)
{
try
{
result = property.GetValue(obj, index);
return result is not null;
}
catch
{
return false;
}
}
MethodInfo method = type.GetMethod(memberName,
index is not null ? [index[0].GetType()] : Type.EmptyTypes);
if (method is not null)
{
try
{
result = method.Invoke(obj, index);
return result is not null;
}
catch
{
return false;
}
}
FieldInfo field = type.GetField(memberName);
if (field is not null)
{
result = field.GetValue(obj);
return result is not null;
}
return false;
}
private static void ParseSegment(string segment, out string memberName, out object[] index)
{
if (segment.Contains('['))
{
memberName = segment.Split('[')[0];
if (string.IsNullOrEmpty(memberName))
memberName = "Item"; // default indexer, e.g. RealParams.[weight]
string indexString = segment[(segment.IndexOf('[') + 1)..^1];
index = [int.TryParse(indexString, out int numeric) ? numeric : indexString];
}
else
{
memberName = segment;
index = null;
}
}
}