using System.Collections; using System.Reflection; using LinqToDB.Extensions; using Pf2eModel.Model.Common.I18n; namespace Pf2eViewer.EntityTreeViewer; internal class StructureBuilder { private readonly List _processedObjects = []; public StructureFieldComplex Create(string name, object value, int depth = 0) { _ = RegisterProcessed(value); StructureFieldComplex field = new(this, value, name, depth); CompleteProcessed(value, field); return field; } public bool RegisterProcessed(object value) { if (_processedObjects.Any(p => p.Value == value)) { return false; } StructureProcessedObject processed = new(value); _processedObjects.Add(processed); return true; } public void CompleteProcessed(object value, StructureFieldComplex field) { var q = _processedObjects.FirstOrDefault(p => p.Value == value); if (q == null) { return; } q.Field = field; } public StructureField Resolve(string name, object? value, int depth) { switch (GetFieldType(value)) { case StructureFieldType.Null: return new StructureFieldNull(this, name, depth); case StructureFieldType.Primitive: return new StructureFieldPrimitive(this, value!, name, depth); case StructureFieldType.Enum: return new StructureFieldEnum(this, value!, name, depth); case StructureFieldType.Complex: default: StructureProcessedObject? processed = _processedObjects.FirstOrDefault(p => p.Value == value ); if (processed is not null) { var reference = new StructureFieldReference(this, name, null, depth); processed.AddReference(reference); return reference; } return Create(name, value!, depth); } } public static StructureFieldType GetFieldType(object? value) { if (value is null) { return StructureFieldType.Null; } Type type = value.GetType(); if (type.IsEnum) { return StructureFieldType.Enum; } if (type.IsPrimitive || type == typeof(string)) { return StructureFieldType.Primitive; } return StructureFieldType.Complex; } } internal class StructureProcessedObject(object value) { public readonly object Value = value; private StructureFieldComplex? _field = null; public StructureFieldComplex? Field { get => _field; set { _field = value; foreach (var reference in _references) { reference.Reference = Field; } } } private readonly List _references = []; public void AddReference(StructureFieldReference reference) { _references.Add(reference); if (_field is not null) { reference.Reference = Field; } } } internal enum StructureFieldType { Null, Primitive, Enum, Complex, Reference, } internal abstract class StructureField(StructureBuilder builder, string name, int depth) { public readonly string Name = name; public abstract StructureFieldType FieldType { get; } public readonly StructureBuilder Builder = builder; public virtual bool Visible { get; set; } = true; public int Depth = depth; public abstract string GetTypeString(); public abstract string GetValueString(); public virtual ICollection GetTextRows(EntityTreeViewerControl control) { return [new EntityTreeViewerRow(control, this)]; } } internal class StructureFieldNull(StructureBuilder builder, string name, int depth) : StructureField(builder, name, depth) { public override StructureFieldType FieldType => StructureFieldType.Null; public override string GetTypeString() => "null"; public override string GetValueString() => ""; } internal class StructureFieldPrimitive( StructureBuilder builder, object value, string name, int depth ) : StructureField(builder, name, depth) { public override StructureFieldType FieldType => StructureFieldType.Primitive; public object Value = value; public readonly Type Type = value.GetType(); public override string GetTypeString() { return Type.GetTypeCode(Type) switch { TypeCode.Boolean => "bool", TypeCode.Int32 => "int", TypeCode.String => "string", TypeCode.Double => "double", _ => Type.Name, }; } public override string GetValueString() { if (Type == typeof(string)) { return "\"" + ((string)Value).Replace("\"", "\\\"") + "\""; } return Value.ToString() ?? string.Empty; } } internal class StructureFieldEnum(StructureBuilder builder, object value, string name, int depth) : StructureField(builder, name, depth) { public override StructureFieldType FieldType => StructureFieldType.Enum; public object Value = value; public readonly Type Type = value.GetType(); public override string GetTypeString() => "enum"; public override string GetValueString() => $"{Type.Name}.{Value}"; } internal class StructureFieldComplex : StructureField { public override StructureFieldType FieldType => StructureFieldType.Complex; public readonly List Fields = []; public readonly Type Type; private bool _collapsed = false; public bool Collapsed { get => _collapsed; set { _collapsed = value; UpdateFieldVisibility(); } } private bool _visible = true; public StructureFieldComplex(StructureBuilder builder, object value, string name, int depth) : base(builder, name, depth) { Type = value.GetType(); SetFields((dynamic)value); } public override bool Visible { get => _visible; set { _visible = value; UpdateFieldVisibility(); } } private void UpdateFieldVisibility() { foreach (var field in Fields) { field.Visible = Visible && !Collapsed; } } private void SetFields(LocaleString localeString) { List records = []; foreach (var field in localeString) { records.Add(new FieldRecord(field.Locale.ToString(), field.Value)); } SetFieldsFromRecords(records); } private void SetFields(IEnumerable enumrable) { int i = 0; List records = []; foreach (var item in enumrable) { records.Add(new FieldRecord($"[{i++}]", item)); } SetFieldsFromRecords(records); } private void SetFields(object value) { Type type = value.GetType(); PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); SetFieldsFromRecords( properties .Where(property => property.GetAttribute() is null ) .Select(property => new FieldRecord(property.Name, property.GetValue(value, null))) .ToList() ); } private void SetFieldsFromRecords(List records) { foreach (FieldRecord record in records) { if (record.FieldType == StructureFieldType.Complex) { record.Allowed = Builder.RegisterProcessed(record.Value!); } } foreach (var record in records) { if (record.FieldType == StructureFieldType.Complex && record.Allowed) { Fields.Add(Builder.Create(record.Name, record.Value!, Depth + 1)); } else { Fields.Add(Builder.Resolve(record.Name, record.Value!, Depth + 1)); } } } public override ICollection GetTextRows(EntityTreeViewerControl control) { List rows = base.GetTextRows(control).ToList(); rows.AddRange(Fields.SelectMany(f => f.GetTextRows(control)).ToList()); return rows; } private class FieldRecord(string name, object? value) { public string Name = name; public object? Value = value; public StructureFieldType FieldType => StructureBuilder.GetFieldType(Value); public bool Allowed = true; } public override string GetTypeString() { if (Type.IsGenericEnumerableType()) { Type elementType = Type.GetGenericArguments().Single(); return $"{elementType.Name}[]"; } else { return Type.Name; } } public override string GetValueString() { if (Type.IsGenericEnumerableType()) { return Fields.Count.ToString() + (Fields.Count == 1 ? " item" : " items"); } return ""; } } internal class StructureFieldReference( StructureBuilder builder, string name, StructureFieldComplex? reference, int depth ) : StructureField(builder, name, depth) { public StructureFieldComplex? Reference = reference; public override StructureFieldType FieldType => StructureFieldType.Reference; public override string GetTypeString() => Reference?.GetTypeString() ?? "???"; public override string GetValueString() => ""; }