using System.Collections;
using System.Reflection;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Options;
using DatReaderWriter.Types;
namespace AcDream.Cli;
///
/// Full reflective dump of a vitals LayoutDesc element tree: every scalar
/// property (position/size/flags) of each ElementDesc + its state sprites,
/// so the real bar rects + spacing + window size can be read from the dat
/// instead of guessed. Uses reflection so it doesn't depend on knowing the
/// DatReaderWriter property names ahead of time.
///
public static class VitalsLayoutDump
{
public static int Run(string datDir, string? layoutIdText)
{
if (!Directory.Exists(datDir)) { Console.Error.WriteLine($"error: dir not found: {datDir}"); return 2; }
using var dats = new DatCollection(datDir, DatAccessType.Read);
// Default to the vitals layout dump-vitals-bars found; allow override.
uint layoutId = 0x21000014u;
if (!string.IsNullOrWhiteSpace(layoutIdText))
{
var t = layoutIdText.Trim();
if (t.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) t = t[2..];
uint.TryParse(t, System.Globalization.NumberStyles.HexNumber, null, out layoutId);
}
// First: scan ALL LayoutDescs that contain a vitals meter element, with root size,
// so we can tell whether 0x21000014 is the one the user sees (row vs stacked).
Console.WriteLine("=== LayoutDescs containing a vitals meter element (0x100000E6/EC/EE) ===");
foreach (var id in dats.GetAllIdsOfType())
{
var l = dats.Get(id);
if (l is null) continue;
if (!ContainsAny(l, 0x100000E6u, 0x100000ECu, 0x100000EEu)) continue;
Console.WriteLine($" 0x{id:X8} {RootSizeSummary(l)}");
}
Console.WriteLine();
var ld = dats.Get(layoutId);
if (ld is null) { Console.Error.WriteLine($"layout 0x{layoutId:X8} not found"); return 1; }
Console.WriteLine($"=== FULL DUMP layout 0x{layoutId:X8} ===");
DumpScalars("LayoutDesc", ld, 0);
foreach (var kv in ld.Elements)
DumpElement(kv.Value, 1);
return 0;
}
private static bool ContainsAny(LayoutDesc l, params uint[] ids)
{
foreach (var kv in l.Elements)
if (ElemContains(kv.Value, ids)) return true;
return false;
}
private static bool ElemContains(ElementDesc e, uint[] ids)
{
if (Array.IndexOf(ids, e.ElementId) >= 0) return true;
foreach (var kv in e.Children)
if (ElemContains(kv.Value, ids)) return true;
return false;
}
private static string RootSizeSummary(LayoutDesc l)
{
// Print any LayoutDesc-level scalar that looks like a size.
var sb = new System.Text.StringBuilder();
foreach (var p in l.GetType().GetProperties())
{
if (p.GetIndexParameters().Length > 0) continue;
if (p.Name is "Elements") continue;
object? v; try { v = p.GetValue(l); } catch { continue; }
if (v is null) continue;
if (IsScalar(v)) sb.Append($"{p.Name}={v} ");
}
return sb.ToString().Trim();
}
private static void DumpElement(ElementDesc e, int depth)
{
string ind = new string(' ', depth * 2);
Console.WriteLine($"{ind}element 0x{e.ElementId:X8}");
DumpScalars(ind + " ", e, depth);
if (e.StateDesc is not null) DumpMedia(ind + " [DirectState]", e.StateDesc);
foreach (var s in e.States)
DumpMedia($"{ind} [state {s.Key}]", s.Value);
foreach (var c in e.Children)
DumpElement(c.Value, depth + 1);
}
private static readonly HashSet Skip = new() { "Children", "States", "StateDesc", "Elements", "Media" };
private static void DumpScalars(string label, object o, int depth)
{
foreach (var (name, val) in Members(o))
{
if (Skip.Contains(name)) continue;
if (IsScalar(val))
Console.WriteLine($"{label} {name} = {Fmt(name, val)}");
}
}
private static void DumpMedia(string label, StateDesc sd)
{
foreach (var m in sd.Media)
{
var sb = new System.Text.StringBuilder();
foreach (var (name, val) in Members(m))
if (IsScalar(val)) sb.Append($"{name}={Fmt(name, val)} ");
Console.WriteLine($"{label} {m.GetType().Name}: {sb.ToString().Trim()}");
}
}
/// Enumerate public properties AND public fields (the DatReaderWriter
/// generated types expose geometry/file ids as fields, not properties).
private static IEnumerable<(string name, object val)> Members(object o)
{
var t = o.GetType();
foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (p.GetIndexParameters().Length > 0) continue;
object? v; try { v = p.GetValue(o); } catch { continue; }
if (v is not null) yield return (p.Name, v);
}
foreach (var f in t.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
object? v; try { v = f.GetValue(o); } catch { continue; }
if (v is not null) yield return (f.Name, v);
}
}
private static string Fmt(string name, object v) =>
name.Contains("File", StringComparison.OrdinalIgnoreCase) && v is uint u ? $"0x{u:X8}" : v.ToString() ?? "";
private static bool IsScalar(object v)
{
var t = v.GetType();
if (v is string) return true;
if (t.IsPrimitive || t.IsEnum) return true;
if (v is IEnumerable) return false;
// value-type structs (Rectangle/Point/etc.) — print via ToString
return t.IsValueType;
}
}