using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace OpenNest.Engine.Jobs.Remote;
///
/// Every engine solve one
/// made, in order: what it asked (part and stock ids with their quantities) and the raw result
/// the engine returned. A remote solve sends this back instead of bound plates, so the client
/// replays the pipeline against its own drawings and validates and binds the result itself
/// (). Poses are kept bit for bit, nonfinite ones included,
/// so the client's validation sees exactly what the engine returned.
///
public sealed class NestSolveTranscript
{
public const int CurrentVersion = 1;
private static readonly JsonSerializerOptions Options = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) },
};
public int Version { get; set; } = CurrentVersion;
public List Solves { get; set; } = new();
public string ToJson() => JsonSerializer.Serialize(this, Options);
/// Reads a transcript; throws when it is not one this build understands.
public static NestSolveTranscript FromJson(string json)
{
NestSolveTranscript transcript;
try { transcript = JsonSerializer.Deserialize(json, Options); }
catch (JsonException ex) { throw new FormatException("The server's nest result is not valid JSON.", ex); }
if (transcript?.Solves == null || transcript.Solves.Any(s => s?.Result == null))
throw new FormatException("The server's nest result is incomplete.");
if (transcript.Version != CurrentVersion)
throw new FormatException($"The server's nest result is version {transcript.Version}; this build reads {CurrentVersion}.");
return transcript;
}
internal void Add(NestJob job, NestJobResult result) =>
Solves.Add(new SolveJson { Request = Fingerprint(job), Result = ResultJson.From(result) });
/// What a solve was asked: the job's part and stock ids with their quantities, in order.
internal static List Fingerprint(NestJob job) =>
job.Parts.Select(p => $"part:{p.Id}:{p.Quantity}")
.Concat(job.Plates.Select(s => $"stock:{s.Id}:{s.Quantity?.ToString() ?? "*"}"))
.ToList();
public sealed class SolveJson
{
public List Request { get; set; } = new();
public ResultJson Result { get; set; }
}
public sealed class ResultJson
{
public NestJobStatus Status { get; set; }
public NestJobStopReason StopReason { get; set; }
public List Plates { get; set; } = new();
public List Fulfillment { get; set; } = new();
public List StockUsage { get; set; } = new();
internal static ResultJson From(NestJobResult result) => new()
{
Status = result.Status,
StopReason = result.StopReason,
Plates = result.Plates.Select(p => new PlateJson
{
PlateIndex = p.PlateIndex,
Stock = NestStockJson.From(p.Stock),
Placements = p.Placements.ToList(),
}).ToList(),
Fulfillment = result.Fulfillment.ToList(),
StockUsage = result.StockUsage.ToList(),
};
/// The raw result, with each sheet that matches one of 's
/// stock entries using that entry itself, as an engine solving the job would.
internal NestJobResult ToResult(NestJob job)
{
var plates = new List(Plates.Count);
foreach (var plate in Plates)
{
var stock = plate.Stock?.ToStock() ?? throw new FormatException("A result sheet has no stock.");
var own = job.Plates.FirstOrDefault(s => RepeatedPlateGrouping.SameStock(s, stock));
plates.Add(new NestJobPlateResult(plate.PlateIndex, own ?? stock, plate.Placements ?? new()));
}
return new NestJobResult(Status, StopReason, plates, Fulfillment ?? new(), StockUsage ?? new());
}
}
public sealed class PlateJson
{
public int PlateIndex { get; set; }
public NestStockJson Stock { get; set; }
public List Placements { get; set; } = new();
}
}