using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using OpenNest.Data.MaterialLedger;
using OpenNest.Engine;
using OpenNest.Engine.Jobs;
namespace OpenNest.Desktop
{
///
/// What Make Nests' renest step kept, its note and the ledger codes' locations.
///
public sealed record ReleaseRenestResult(bool Nested, string Note,
IReadOnlyDictionary LedgerLocations);
///
/// Make Nests' Auto Nest of one released nest: on the run's MaterialLedger stock first,
/// then on the nest's plate defaults (), with the
/// ledger codes given to the plates inside each commit, so they are part of its undo step.
/// Shared by the headless and the live (main window) release.
///
public static class ReleaseRenest
{
///
/// Attaches a kept result's sheets to the nest's plates and returns them. It must call
/// finish once with those plates before its undo step closes; that gives them
/// their ledger codes. Not calling it, or calling it twice, throws.
///
public delegate IReadOnlyList Commit(NestPipelineResult result,
Action> finish);
///
/// Nests , which has no parts placed, on 's
/// stock (null: the plate defaults only) and then its plate defaults. The ledger is not
/// read when nothing has a quantity to nest.
///
public static async Task RunAsync(Nest nest, ReleaseLedgerStock ledger,
string engineName, double minimumSalvageDimension,
Func> solve, Commit commit,
CancellationToken token)
{
ArgumentNullException.ThrowIfNull(nest);
ArgumentNullException.ThrowIfNull(solve);
ArgumentNullException.ThrowIfNull(commit);
var wanted = nest.Drawings.Any(d => !d.IsCutOff && d.Quantity.Remaining > 0);
var offer = ledger != null && wanted ? await ledger.OfferAsync(nest, token) : null;
var locations = new Dictionary(StringComparer.Ordinal);
var sheets = 0;
var renested = await NestRenest.RunStagedAsync(nest, engineName, offer?.Options,
minimumSalvageDimension, solve,
result =>
{
var finished = false;
var plates = commit(result, applied =>
{
if (finished)
throw new InvalidOperationException(
"A release commit finished its plates twice.");
finished = true;
if (offer == null)
return;
var stock = NestPipelineCommit.PairWithStock(result, applied)
.Select(p => (p.Plate, p.Stock.Id))
.ToList();
foreach (var (code, location) in ledger.AssignCodes(nest, offer, stock))
locations[code] = location;
sheets += stock.Sum(p => p.Plate.StockItemCodes.Count);
});
if (!finished)
throw new InvalidOperationException(
"A release commit did not finish its plates, so they have no codes.");
return plates;
},
token);
var ledgerNote = offer == null
? null
: ReleaseLedgerStock.Note(offer, sheets, renested.FirstStockNotKept);
return new ReleaseRenestResult(renested.Nested, Join(renested.Note, ledgerNote),
locations);
}
///
/// for a nest no one is looking at: every plate is removed, the
/// layouts are solved on the calling thread and attached as new plates.
///
public static Task RunHeadlessAsync(Nest nest,
ReleaseLedgerStock ledger, INestingEngine engine, string engineName,
double minimumSalvageDimension,
CancellationToken token)
{
ArgumentNullException.ThrowIfNull(nest);
ArgumentNullException.ThrowIfNull(engine);
token.ThrowIfCancellationRequested();
nest.Plates.Clear();
nest.UpdateDrawingQuantities();
return RunAsync(nest, ledger, engineName, minimumSalvageDimension,
request => Task.FromResult(
NestPipeline.Run(engine, engineName, request, null, token)),
(result, finish) =>
{
var plates = NestPipelineCommit.ApplyToPlates(result, nest.CreatePlate,
token: token);
finish(plates);
return plates;
},
token);
}
/// Auto Nest's note, then what MaterialLedger stock the nest used.
public static string Join(string renestNote, string ledgerNote) =>
string.Join(" ", new[] { renestNote, ledgerNote }.Where(n => !string.IsNullOrEmpty(n)));
}
}