using System;
using System.Collections.Generic;
using System.Threading;
namespace OpenNest;
///
/// Migrated built-in placement strategy for the whole-job runner. Reuses
/// fill/pack geometry but owns its own run-scoped bookkeeping: remaining demand is read from the
/// request and placement counts are derived from returned placements, so the engine's private
/// mutations never feed back into job accounting.
///
///
/// A private per requirement is created once per solve and reused across every
/// candidate trial (the runner reuses one instance per job). This is safe
/// because the engines mutate (per-trial) and canonical-frame copies,
/// never the shared or its Quantity. Identity is by Drawing reference,
/// never by name. Each trial still gets a fresh private .
///
public sealed class DefaultPlateNester : IPlateNester
{
private readonly Func engineFactory;
private readonly Dictionary drawingsById = new(StringComparer.Ordinal);
private readonly Dictionary idByDrawing = new(ReferenceEqualityComparer.Instance);
public DefaultPlateNester() : this(static plate => new DefaultNestEngine(plate))
{
}
/// Injectable for tests; defaults to .
public DefaultPlateNester(Func engineFactory)
{
this.engineFactory = engineFactory ?? throw new ArgumentNullException(nameof(engineFactory));
}
public PlateCandidate Place(PlatePlacementRequest request, IProgress progress = null,
CancellationToken token = default)
{
ArgumentNullException.ThrowIfNull(request);
token.ThrowIfCancellationRequested();
var plate = DrawingJobMapper.CreatePlate(request.Stock);
var items = new List(request.Parts.Count);
foreach (var requirement in request.Parts)
{
if (!drawingsById.TryGetValue(requirement.Id, out var drawing))
{
drawing = DrawingJobMapper.CreateDrawing(requirement);
drawingsById.Add(requirement.Id, drawing);
idByDrawing.Add(drawing, requirement.Id);
}
// Quantity is the request's remaining demand; the engine may mutate this per-trial item,
// and that mutation is deliberately discarded — placement counts come from the result.
items.Add(new NestItem
{
Drawing = drawing,
Quantity = requirement.Quantity,
Priority = requirement.Priority,
StepAngle = DrawingJobMapper.LegacyStep(requirement.Rotation),
RotationStart = requirement.Rotation.Start,
RotationEnd = requirement.Rotation.End
});
}
var engine = engineFactory(plate) ?? throw new InvalidOperationException("Engine factory returned null.");
var legacyProgress = CandidateProgressBridge.Create(progress, request.Stock.Id);
var parts = engine.Nest(items, legacyProgress, token);
token.ThrowIfCancellationRequested();
if (parts == null) throw new InvalidOperationException("Engine returned null placements.");
var placements = new List(parts.Count);
foreach (var part in parts)
{
if (part?.BaseDrawing == null || !idByDrawing.TryGetValue(part.BaseDrawing, out var id))
throw new InvalidOperationException("Placement does not reference a known requirement drawing.");
placements.Add(new NestJobPlacement(id, 0, part.Location.X, part.Location.Y, part.Rotation));
}
return new PlateCandidate(placements);
}
}