using System;
using System.Collections.Generic;
namespace OpenNest.CNC
{
///
/// The head-height lock around a cut-off's overcut past the nominal far sheet edge. Stock can
/// arrive wider than nominal, so a cut-off overcuts past the far edge to cut the oversize off;
/// past the real edge there is no material to sense, so the head height is locked first. A
/// cut-off program marks the locked stretch with a comment and ends it with an
/// comment; a rapid between them keeps the head locked and is not a head
/// raise. Posts write each marker as their machine's lock or unlock code and refuse to post a
/// marked program when no code is configured, because the overcut is unsafe without the lock.
/// See docs/automatic-scrap-cutoffs.md.
///
public static class HeadHeightLock
{
/// The comment before the first motion made with the head height locked.
public const string Lock = "HEAD HEIGHT LOCK";
/// The comment after the last motion made with the head height locked.
public const string Unlock = "HEAD HEIGHT UNLOCK";
public static bool IsLock(ICode code) => code is Comment { Value: Lock };
public static bool IsUnlock(ICode code) => code is Comment { Value: Unlock };
/// Whether 's own codes hold a lock marker.
public static bool IsMarked(Program program)
{
if (program?.Codes == null)
return false;
foreach (var code in program.Codes)
if (IsLock(code))
return true;
return false;
}
///
/// For each of , whether the head height is locked while it runs: the
/// codes after a lock marker up to its unlock marker. Markers must alternate, starting with a
/// lock and ending unlocked; anything else is a malformed program (),
/// since a post that lost track of the lock could cut off the sheet unlocked or never unlock.
///
public static bool[] LockedMask(IReadOnlyList codes)
{
ArgumentNullException.ThrowIfNull(codes);
var mask = new bool[codes.Count];
var locked = false;
for (var i = 0; i < codes.Count; i++)
{
if (IsLock(codes[i]))
{
if (locked)
throw new ArgumentException("A head-height lock is set twice without an unlock.");
locked = true;
}
else if (IsUnlock(codes[i]))
{
if (!locked)
throw new ArgumentException("A head-height unlock has no lock before it.");
locked = false;
}
else
mask[i] = locked;
}
if (locked)
throw new ArgumentException("A head-height lock is never released.");
return mask;
}
///
/// Refuses a nest a post cannot write, before any output is created: a cut-off with a locked
/// overcut needs the post's lock and unlock codes, and its markers must be well formed.
///
public static void EnsurePostable(Nest nest, bool hasCodes, string postName) =>
PostMarkers.EnsurePostable(nest, hasCodes, IsMarked, program => LockedMask(program.Codes),
name => $"{name} overcuts past the sheet edge with the head height locked, but the "
+ $"{postName} post has no head-height lock and unlock codes. Enter them in "
+ $"\"Head height lock\" and \"Head height unlock\" in the {postName} Settings "
+ "dialog, which opens when you post, or delete the cut-off and add it again "
+ "with \"Lock head height and overcut the far edge\" unchecked.");
}
}