using System.Globalization;
using System.Text.Json;
namespace XFEEDToolBox.Core.Galaxy;
public sealed record GalaxyRouteFilterChoice(int Index,string SettingsName,string Key,bool Selected);
public sealed record GalaxyRouteFilterCategoryChoices(int CategoryId,IReadOnlyList<GalaxyRouteFilterChoice>? Choices,bool? AffectsRoutePlotting);
public readonly record struct GalaxyRouteChoicesSourceKey(int ProcessId,long Epoch,long ContextSystemAddress)
{public override string ToString()=>FormattableString.Invariant($"{ProcessId}|{Epoch}|{ContextSystemAddress}");}
/// <summary>Current provider rows, never inferred public enums or an XML preset.
/// Unknown categories are explicit null rows. Applying them never edits Values.</summary>
public sealed record GalaxyCurrentRouteChoices(ulong CapturedAtTick,long ContextSystemAddress,IReadOnlyList<GalaxyRouteFilterCategoryChoices> Categories)
{
private static readonly IReadOnlyDictionary<int,GalaxyFilterCategory> categories=new Dictionary<int,GalaxyFilterCategory>
{{2,GalaxyFilterCategory.Allegiance},{3,GalaxyFilterCategory.Government},{4,GalaxyFilterCategory.Economy},{6,GalaxyFilterCategory.State},
{7,GalaxyFilterCategory.Security},{8,GalaxyFilterCategory.Visited},{10,GalaxyFilterCategory.Civilization}};
public static GalaxyCurrentRouteChoices? FromWire(JsonElement value)
{
if(value.ValueKind==JsonValueKind.Null)return null;
try
{
if(value.ValueKind!=JsonValueKind.Object||value.GetProperty("scope").GetString()!="current-original-route-filter-choices")throw new InvalidDataException("原路线选项来源无效。");
ulong Exact(string key)=>value.GetProperty(key).ValueKind==JsonValueKind.String&&ulong.TryParse(value.GetProperty(key).GetString(),NumberStyles.None,CultureInfo.InvariantCulture,out var n)?n:throw new InvalidDataException("原路线选项来源整数无效。");
var tick=Exact("capturedAtTick");var origin=Exact("contextSystemAddress");if(origin==0||origin>=1UL<<55)throw new InvalidDataException("原路线选项参照星系无效。");
var input=value.GetProperty("categories");if(input.ValueKind!=JsonValueKind.Array||input.GetArrayLength()!=7)throw new InvalidDataException("原路线选项类别不完整。");
var result=new List<GalaxyRouteFilterCategoryChoices>();var found=new HashSet<int>();
foreach(var row in input.EnumerateArray())
{
var id=row.GetProperty("categoryId").GetInt32();if(!categories.ContainsKey(id)||!found.Add(id))throw new InvalidDataException("原路线选项类别无效。");
var choices=row.GetProperty("choices");var affects=row.GetProperty("affectsRoutePlotting");
if(choices.ValueKind==JsonValueKind.Null)
{if(affects.ValueKind!=JsonValueKind.Null)throw new InvalidDataException("未知原路线选项带有已知开关。");result.Add(new(id,null,null));continue;}
if(choices.ValueKind!=JsonValueKind.Array||choices.GetArrayLength() is <1 or >96||affects.ValueKind is not(JsonValueKind.True or JsonValueKind.False))throw new InvalidDataException("原路线选项资料无效。");
var entries=new List<GalaxyRouteFilterChoice>();var indices=new HashSet<int>();var names=new HashSet<string>(StringComparer.Ordinal);
foreach(var choice in choices.EnumerateArray())
{
var index=choice.GetProperty("index").GetInt32();var settings=choice.GetProperty("settingsName").GetString();var key=choice.GetProperty("key").GetString();var selected=choice.GetProperty("selected");
if(index<0||index>=choices.GetArrayLength()||!indices.Add(index)||string.IsNullOrWhiteSpace(settings)||settings.Length>128||
string.IsNullOrEmpty(key)||key!=GalaxyMapFilter.OriginalRouteKey(settings)||!names.Add(key)||selected.ValueKind is not(JsonValueKind.True or JsonValueKind.False))throw new InvalidDataException("原路线选项名称或索引无效。");
entries.Add(new(index,settings,key,selected.GetBoolean()));
}
result.Add(new(id,entries.OrderBy(c=>c.Index).ToArray(),affects.GetBoolean()));
}
return new(tick,(long)origin,result.OrderBy(c=>c.CategoryId).ToArray());
}
catch(Exception error)when(error is KeyNotFoundException or InvalidOperationException or FormatException or OverflowException)
{throw new InvalidDataException("原路线选项回执无效。",error);}
}
public GalaxyMapFilter MergeInto(GalaxyMapFilter filter)=>Merge(filter,Categories);
public static GalaxyMapFilter ClearLive(GalaxyMapFilter filter)=>Merge(filter,[]);
private static GalaxyMapFilter Merge(GalaxyMapFilter filter,IReadOnlyList<GalaxyRouteFilterCategoryChoices> input)
{
var options=new Dictionary<GalaxyFilterCategory,IReadOnlySet<string>>(filter.OriginalRouteOptionKeys);
var selected=new Dictionary<GalaxyFilterCategory,IReadOnlySet<string>>(filter.OriginalRouteSelectedKeys);
var affects=new Dictionary<GalaxyFilterCategory,bool>(filter.OriginalRouteFilters);var changed=false;
foreach(var pair in categories)
{
var row=input.SingleOrDefault(c=>c.CategoryId==pair.Key);var category=pair.Value;
if(row?.Choices is null||row.AffectsRoutePlotting is null){changed|=options.Remove(category)|selected.Remove(category)|affects.Remove(category);continue;}
var keys=row.Choices.Select(c=>c.Key).ToHashSet(StringComparer.Ordinal);var enabled=row.Choices.Where(c=>c.Selected).Select(c=>c.Key).ToHashSet(StringComparer.Ordinal);
if(!options.TryGetValue(category,out var oldKeys)||!oldKeys.SetEquals(keys)){options[category]=keys;changed=true;}
if(!selected.TryGetValue(category,out var oldSelected)||!oldSelected.SetEquals(enabled)){selected[category]=enabled;changed=true;}
if(!affects.TryGetValue(category,out var oldAffects)||oldAffects!=row.AffectsRoutePlotting){affects[category]=row.AffectsRoutePlotting!.Value;changed=true;}
}
return changed?filter with{OriginalRouteOptionKeys=options,OriginalRouteSelectedKeys=selected,OriginalRouteFilters=affects}:filter;
}
}
public sealed partial record GalaxyCatalogueProfile {public GalaxyCurrentRouteChoices? CurrentRouteChoices {get;init;}}
using System.Globalization;
using System.Text.Json;
namespace XFEEDToolBox.Core.Galaxy;
public sealed record GalaxyRouteFilterChoice(int Index,string SettingsName,string Key,bool Selected);
public sealed record GalaxyRouteFilterCategoryChoices(int CategoryId,IReadOnlyList<GalaxyRouteFilterChoice>? Choices,bool? AffectsRoutePlotting);
public readonly record struct GalaxyRouteChoicesSourceKey(int ProcessId,long Epoch,long ContextSystemAddress)
{public override string ToString()=>FormattableString.Invariant($"{ProcessId}|{Epoch}|{ContextSystemAddress}");}
/// <summary>Current provider rows, never inferred public enums or an XML preset.
/// Unknown categories are explicit null rows. Applying them never edits Values.</summary>
public sealed record GalaxyCurrentRouteChoices(ulong CapturedAtTick,long ContextSystemAddress,IReadOnlyList<GalaxyRouteFilterCategoryChoices> Categories)
{
private static readonly IReadOnlyDictionary<int,GalaxyFilterCategory> categories=new Dictionary<int,GalaxyFilterCategory>
{{2,GalaxyFilterCategory.Allegiance},{3,GalaxyFilterCategory.Government},{4,GalaxyFilterCategory.Economy},{6,GalaxyFilterCategory.State},
{7,GalaxyFilterCategory.Security},{8,GalaxyFilterCategory.Visited},{10,GalaxyFilterCategory.Civilization}};
public static GalaxyCurrentRouteChoices? FromWire(JsonElement value)
{
if(value.ValueKind==JsonValueKind.Null)return null;
try
{
if(value.ValueKind!=JsonValueKind.Object||value.GetProperty("scope").GetString()!="current-original-route-filter-choices")throw new InvalidDataException("原路线选项来源无效。");
ulong Exact(string key)=>value.GetProperty(key).ValueKind==JsonValueKind.String&&ulong.TryParse(value.GetProperty(key).GetString(),NumberStyles.None,CultureInfo.InvariantCulture,out var n)?n:throw new InvalidDataException("原路线选项来源整数无效。");
var tick=Exact("capturedAtTick");var origin=Exact("contextSystemAddress");if(origin==0||origin>=1UL<<55)throw new InvalidDataException("原路线选项参照星系无效。");
var input=value.GetProperty("categories");if(input.ValueKind!=JsonValueKind.Array||input.GetArrayLength()!=7)throw new InvalidDataException("原路线选项类别不完整。");
var result=new List<GalaxyRouteFilterCategoryChoices>();var found=new HashSet<int>();
foreach(var row in input.EnumerateArray())
{
var id=row.GetProperty("categoryId").GetInt32();if(!categories.ContainsKey(id)||!found.Add(id))throw new InvalidDataException("原路线选项类别无效。");
var choices=row.GetProperty("choices");var affects=row.GetProperty("affectsRoutePlotting");
if(choices.ValueKind==JsonValueKind.Null)
{if(affects.ValueKind!=JsonValueKind.Null)throw new InvalidDataException("未知原路线选项带有已知开关。");result.Add(new(id,null,null));continue;}
if(choices.ValueKind!=JsonValueKind.Array||choices.GetArrayLength() is <1 or >96||affects.ValueKind is not(JsonValueKind.True or JsonValueKind.False))throw new InvalidDataException("原路线选项资料无效。");
var entries=new List<GalaxyRouteFilterChoice>();var indices=new HashSet<int>();var names=new HashSet<string>(StringComparer.Ordinal);
foreach(var choice in choices.EnumerateArray())
{
var index=choice.GetProperty("index").GetInt32();var settings=choice.GetProperty("settingsName").GetString();var key=choice.GetProperty("key").GetString();var selected=choice.GetProperty("selected");
if(index<0||index>=choices.GetArrayLength()||!indices.Add(index)||string.IsNullOrWhiteSpace(settings)||settings.Length>128||
string.IsNullOrEmpty(key)||key!=GalaxyMapFilter.OriginalRouteKey(settings)||!names.Add(key)||selected.ValueKind is not(JsonValueKind.True or JsonValueKind.False))throw new InvalidDataException("原路线选项名称或索引无效。");
entries.Add(new(index,settings,key,selected.GetBoolean()));
}
result.Add(new(id,entries.OrderBy(c=>c.Index).ToArray(),affects.GetBoolean()));
}
return new(tick,(long)origin,result.OrderBy(c=>c.CategoryId).ToArray());
}
catch(Exception error)when(error is KeyNotFoundException or InvalidOperationException or FormatException or OverflowException)
{throw new InvalidDataException("原路线选项回执无效。",error);}
}
public GalaxyMapFilter MergeInto(GalaxyMapFilter filter)=>Merge(filter,Categories);
public static GalaxyMapFilter ClearLive(GalaxyMapFilter filter)=>Merge(filter,[]);
private static GalaxyMapFilter Merge(GalaxyMapFilter filter,IReadOnlyList<GalaxyRouteFilterCategoryChoices> input)
{
var options=new Dictionary<GalaxyFilterCategory,IReadOnlySet<string>>(filter.OriginalRouteOptionKeys);
var selected=new Dictionary<GalaxyFilterCategory,IReadOnlySet<string>>(filter.OriginalRouteSelectedKeys);
var affects=new Dictionary<GalaxyFilterCategory,bool>(filter.OriginalRouteFilters);var changed=false;
foreach(var pair in categories)
{
var row=input.SingleOrDefault(c=>c.CategoryId==pair.Key);var category=pair.Value;
if(row?.Choices is null||row.AffectsRoutePlotting is null){changed|=options.Remove(category)|selected.Remove(category)|affects.Remove(category);continue;}
var keys=row.Choices.Select(c=>c.Key).ToHashSet(StringComparer.Ordinal);var enabled=row.Choices.Where(c=>c.Selected).Select(c=>c.Key).ToHashSet(StringComparer.Ordinal);
if(!options.TryGetValue(category,out var oldKeys)||!oldKeys.SetEquals(keys)){options[category]=keys;changed=true;}
if(!selected.TryGetValue(category,out var oldSelected)||!oldSelected.SetEquals(enabled)){selected[category]=enabled;changed=true;}
if(!affects.TryGetValue(category,out var oldAffects)||oldAffects!=row.AffectsRoutePlotting){affects[category]=row.AffectsRoutePlotting!.Value;changed=true;}
}
return changed?filter with{OriginalRouteOptionKeys=options,OriginalRouteSelectedKeys=selected,OriginalRouteFilters=affects}:filter;
}
}
public sealed partial record GalaxyCatalogueProfile {public GalaxyCurrentRouteChoices? CurrentRouteChoices {get;init;}}