using System.Globalization;
using System.Text.Json;
namespace XFEEDToolBox.Core.Galaxy;
public sealed record OriginalTradeRoute(long FromSystemAddress,long ToSystemAddress,string Commodity,double NpcAmount,double PlayerAmount);
/// Original account trade routes at their recorded timestamp. Missing files or routes never imply no data is available.
public sealed record OriginalTradeDataSnapshot(ulong CommanderId,long SystemAddress,DateTimeOffset Timestamp,IReadOnlyList Routes,string SourcePath)
{
public bool HasRecordedTradeData=>true;
}
public static class OriginalTradeDataCache
{
public const int MaximumBytes=8*1024*1024,MaximumRoutes=100_000;
public static OriginalTradeDataSnapshot ReadForCommander(string gameCacheDirectory,ulong commanderId,long systemAddress)
{
if(commanderId==0||systemAddress<=0)throw new ArgumentException("原游戏贸易缓存账号或星系地址无效。");
return Read(Path.Combine(gameCacheDirectory,commanderId.ToString(CultureInfo.InvariantCulture),systemAddress.ToString(CultureInfo.InvariantCulture)+".trd"),commanderId,systemAddress);
}
public static OriginalTradeDataSnapshot Read(string path,ulong expectedCommanderId,long expectedSystemAddress)
{
using var file=new FileStream(path,FileMode.Open,FileAccess.Read,FileShare.ReadWrite|FileShare.Delete);
if(file.Length>MaximumBytes)throw new InvalidDataException("原游戏贸易路线缓存超过长度限制。");
var bytes=new byte[checked((int)file.Length)];file.ReadExactly(bytes);
return Parse(bytes,expectedCommanderId,expectedSystemAddress) with{SourcePath=Path.GetFullPath(path)};
}
public static OriginalTradeDataSnapshot Parse(ReadOnlyMemory bytes,ulong expectedCommanderId,long expectedSystemAddress)
{
if(expectedCommanderId==0||expectedSystemAddress<=0||bytes.Length>MaximumBytes)throw new InvalidDataException("原游戏贸易缓存标识或长度无效。");
using var document=JsonDocument.Parse(bytes,new JsonDocumentOptions{MaxDepth=16});var root=document.RootElement;
if(root.GetProperty("version").GetInt32()!=4||root.GetProperty("systemaddress").GetInt64()!=expectedSystemAddress)throw new InvalidDataException("原游戏贸易缓存版本或星系地址不符。");
var rows=root.GetProperty("traderoutes");if(rows.ValueKind!=JsonValueKind.Array||rows.GetArrayLength()>MaximumRoutes)throw new InvalidDataException("原游戏贸易路线记录无效。");
static double Amount(JsonElement row,string key)
{
if(!row.TryGetProperty(key,out var value))return 0;
var valid=value.ValueKind==JsonValueKind.Number?value.TryGetDouble(out var number)?number:double.NaN:
value.ValueKind==JsonValueKind.String&&double.TryParse(value.GetString(),NumberStyles.Float,CultureInfo.InvariantCulture,out number)?number:double.NaN;
if(!double.IsFinite(valid)||valid<0)throw new InvalidDataException("原游戏贸易路线流量无效。");return valid;
}
var routes=new List(rows.GetArrayLength());
foreach(var row in rows.EnumerateArray())
{
var from=row.GetProperty("FromStarID").GetInt64();var to=row.GetProperty("ToStarID").GetInt64();var commodity=row.GetProperty("name").GetString();
if(from<=0||to<=0||string.IsNullOrWhiteSpace(commodity)||commodity.Length>128||from!=expectedSystemAddress&&to!=expectedSystemAddress)throw new InvalidDataException("原游戏贸易路线标识无效。");
routes.Add(new(from,to,commodity,Amount(row,"npcamount"),Amount(row,"playeramount")));
}
var timestamp=DateTimeOffset.FromUnixTimeSeconds(root.GetProperty("timestamp").GetInt64());
return new(expectedCommanderId,expectedSystemAddress,timestamp,routes.AsReadOnly(),"");
}
}