/*
    This file is part of Msc-generator.
	Copyright (C) 2008-2024 Zoltan Turanyi
	Distributed under GNU Affero General Public License.

    Msc-generator is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Msc-generator is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/
/** @file block_blocks.h The declaration for Blocks
* @ingroup libblock_files */

#ifndef BLOCK_BLOCKS_H
#define BLOCK_BLOCKS_H

#include <algorithm>
#include <unordered_set>
#include <optional>
#include "blockstyle.h"
#include "blockcsh.h"

/** This is the namespace containing block chart elements, except parsing.*/
namespace block
{

class BlockChart;
class BlockInstruction;
class BlockBlock;
class MultiBlock;
typedef UPtrList<BlockInstruction> BlockInstrList;
typedef UPtrList<BlockBlock> BlockBlockList;

/** Generates a full name for a block form the name given to it by the user
 * and the full name of the parent. Rules are
 * - Named blocks which are children of a named parent append their name with a dot.
 * - Unnamed blocks' full name is the same as that of their parent plus a dot.
 * - Unnamed blocks, whose parent is also unnamed (hence their name ends in a dot)
 *   have the same name as their parent. A.k.a. we add no second dot.
 * - Named blocks where parents are unnamed, just add the name (to avoid double dots).
 *
 * Note: 'name' may also contain dots, we apply the same rules here.*/
std::string FormChildName(std::string_view parent, std::string_view name);

/** A blockchart element: a block or an arrow.*/
class BlockElement : public Element
{
protected:
    BlockBlock *parent=nullptr;     ///<Our parent. Set in a call to our parent's SetContent
public:
    static const std::vector<std::string> spec_block_names;
    using SpecBlockPtrs = std::array<const BlockBlock*, 5>;
    bool valid = true;              ///<Set to false, if constructor fails, the arrow endpoints don't exist or its path cannot be found. Drop this instruction in PostParseProcess()
    BlockChart    &chart;           ///<The block chart this element belongs to.
    const std::string name_prefix;  ///<Our prefix (without the dot): the full name of our parent (may be equal to the full name of its parent if our parent has no name). Empty only at top level.
    BlockStyle     style;           ///<The style of this element
    Label          parsed_label;    ///<The label processed and parsed into lines. Set in PostParseProcess()
    StringFormat   basic_format;    ///<The formatting at the start of the label. Set in PostParseProcess(), used in FinalizeLabels()
    int            concrete_number; ///<Negative, if the user specified no specific value for the number (no numbering or automatic). Else the number specified.
    NumberingStyle numberingStyle;  ///<The numbering style to use. (This is not part of Styles in general, but is a property of contexts. This is a snapshot at the location of the arc.
    std::string    number_text;     ///<The formatted number assigned to this arc (used by references and notes/comments). Set in PostParseProcess()
    std::string    wildcard_repl;   ///<What to replace \* escapes in labels.
    bool delay_label_drawing = false; ///<True if our label shall be drawn after the block (used by joins with fills)
    mutable Block label_block;      ///<The calculated location of the label (if any). For label_rotate = 90 and 180 degrees, this Block is already rotated by 90 or 270 degrees. For other angles, it needs to be rotated around its center point.
    mutable double label_rotate = 0; ///<The rotation of the label (if any) clockwise in degrees.

    explicit BlockElement(BlockChart &chart);
    BlockElement(BlockChart &chart, const FileLineColRange &l);
    BlockElement(const BlockElement &o, const FileLineColRange &l, std::string_view prefix);
    BlockElement(const BlockElement &o) : BlockElement(o, o.file_pos, o.name_prefix) {}
    BlockElement(BlockElement &&) = delete;

    /** Add to the drawing list (at the end or before an element). */
    virtual void AddToDrawOrder(const BlockElement *blk = nullptr, bool before = true);
    /** Add to the drawing list (at the end or before a set of elements). */
    virtual void AddToDrawOrder(const StringWithPosList &list, bool before = true);
    /** Add an element with its content to the drawing list (at the end or before
     * another element). The content is inserted *after* the element.*/
    void AddToDrawOrderWithContent(const BlockInstrList & content, const BlockElement * blk, bool before);
    /** Add an element with its content to the drawing list (at the end or before
    * a set of elements). The content is inserted *after* the element.*/
    void AddToDrawOrderWithContent(const BlockInstrList & content, const StringWithPosList & list, bool before);
    /** Remove us from the chart's drawing order. */
    void RemoveFromDrawOrder() const;
    /** Finds the block in the draw order, before which we need to insert.
     * This is 'blk' if before==true, and the element after, if 'before'==false.*/
    std::vector<BlockElement*>::iterator FindInDrawOrder(const BlockElement *blk, bool before);
    /** Finds the block in the draw order, before which we need to insert.
    * This is the earliest of 'list' if before==true, and the element after the latest of 'list', if 'before'==false.*/
    std::vector<BlockElement*>::iterator FindInDrawOrder(const StringWithPosList &list, bool before);
    /** Set a block as our parent. */
    void SetParent(BlockBlock *b) { parent = b; }
    /** Get our parent */
    BlockBlock *GetParent() const { return parent; }
    /** Add the attribute names we take to `csh`.*/
    static void AttributeNames(Csh &, bool number);
    /** Add a list of possible attribute value names to `csh` for attribute `attr`.*/
    static bool AttributeValues(std::string_view attr, Csh &csh, bool number);
    virtual bool AddAttribute(const Attribute &a);
    virtual void AddAttributeList(gsl::owner<AttributeList*>a);
    template <typename str_pos_vector>
    bool Resolve(str_pos_vector &blocks, SpecBlockPtrs spec_blocks,
                 std::string_view break_msg, std::string_view ambigous_msg,
                 std::string_view spec_block_msg, std::string *text, bool *justify);
    struct HandleHidden {
        enum HandleHiddenType { Keep, Remove, ToParent };
        HandleHiddenType action;
        std::string_view message = {}; //This message is displayed as an error if non-empty. Replace %B to the parent collapsed and %b to the block removed.
    };
    template <typename str_pos_vector>
    bool CheckExists(str_pos_vector &blocks, std::string_view whattoignore,
                     HandleHidden handle_hidden, std::string_view self_msg = {}, bool keep_last = false);
    virtual void MakeTemplate() { RemoveFromDrawOrder(); }

    virtual bool PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number);
    virtual void FinalizeLabel(Canvas &canvas);
    void ShiftBy(const XY &xy) override { label_block.Shift(xy); Element::ShiftBy(xy); }
    void PostPosProcess(Canvas &canvas, Chart *ch) override;
    void CollectIsMapElements(Canvas &) override;
    void RegisterLabels() override;
    Contour LabelCover() const;
    void DrawLabel(Canvas &canvas, bool even_if_delayed=false) const;
    virtual void Draw(Canvas&) const {}
    ~BlockElement() override { RemoveFromDrawOrder(); }
};

/** Describes one variable used during layouting.
 * It represents an x or an y coordinate of one aspect of a block
 * (side, inner margin or content)*/
struct Variable {
    std::string name;      ///<The name of the variable. Strictly for debugging.
    const BlockBlock *const block;
    struct Mapping {
        unsigned var = 0;  ///<The number of the GLPK column (variable) to map to
        double off = 0;    ///<Add this much to the value of the GLPK column
    } mapped_to;           ///<The GLPK variable this external variable is mapped to (and the offset to add to that)
    double      value = 0; ///<The (computed) value of the variable. Ready after layout.
    /** Get the value of the variable.*/
    double Get() const noexcept { return value; }
};

struct VariableOffset
{
    unsigned var;
    double offset;
};

/** During Layout this describes a relation between variables.
 * It is ether an equality or inequality.*/
struct Constraint
{
    /** Describes one side of the constraint.*/
    struct Side
    {
        unsigned  v1;    ///<The first (or only) variable
        unsigned  v2;    ///<The second variable.
        double    w1;    ///<The weight of the first variable
        double    w2;    ///<The weight of the second variable
        double    offset;///<A fixed value to be added to the (sum/avg of) variable(s).
        std::string Print(const std::vector<std::string> &Variables);
        /** Returns true if the variables are the same including weights.*/
        constexpr bool VariablesEqual(const Side &o) const noexcept {
            return (v1 == o.v1 && w1==o.w1 && (w2==0 || v2==o.v2) && w2==o.w2) ||
                   (v1 == o.v2 && w1==o.w2 && (w2==0 || v2==o.v1) && w2==o.w1);
        }
        /** True if this side is just a single (1-weight) variable with perhaps an offset.*/
        constexpr bool is_simple_variable() const noexcept { return w1==1 && w2==0; }
    };
    /** Enumerates the possible Constraint types.*/
    enum EType
    {
        EQUAL,         ///<Equality, s1==s2
        INEQUAL,       ///<Greater than, s1>=s2
        VEC_MIN_EQUAL, ///<Minimum. s1=minimum of the variables in 'vec'. s1 must be SINGLE and no offset. 's2' is ignored.
        VEC_MAX_EQUAL, ///<Maximum. s1=maximum of the variables in 'vec'. s1 must be SINGLE and no offset. 's2' is ignored.
        DIFF_MIN       ///<The diff of the two variables (s1-s2) shall be minimal
    };
    /** Validity Types*/
    enum class EValidity
    {
        Valid,      ///<Valid constraint that we can (or shall try to) fulfill
        Conflicted, ///<A constraint we have found to be conflicting and should not be registered
        Pruned,     ///<A constraint that has a stronger constraint before it - we can ignore it and assume it holding
    };
    /** Enumerates the comparison of two constraints how can they hold or be violated.*/
    enum class ERelation
    {
        Unrelated, ///<The two constraints are not related. Any of them can hold or conflict independently.
        Stronger,  ///<The first constraint is stronger: whenever it holds, the second holds, too
        Weaker,    ///<The first constraint is weaker: whenever the second holds, the first one holds, too
        Equivalent ///<The two constraints hold exactly in the same situations.
    };

    EType      type; ///<The type of constraint we are. Determines if 's2' or 'vec' is ignored.
    EAlignPrio prio; ///<What is the priority of the attribute resulting in us. This can be changed and is used to sort constraints.
    Side       s1;   ///<First side
    Side       s2;   ///<Second side
    std::vector<VariableOffset> vec;    ///<In case of min/max constraints, the list of variables, with offset

    const BlockBlock *block = nullptr;  ///<The block generated this constraint.
    FileLineCol       attr_pos;         ///<The position of the attribute that generated this constraint. May be invalid for default or auto attributes from default styles.
    FileLineCol       justify_attr_pos; ///<If this constaint is 'follow the prev block' type of attribute and we are justifying, this is the location of the parent's justify attribute.
    EAlignPrio        display_prio;     ///<The original priority, used to display a correct message to the user.
    std::string       attr_text;        ///<The human readable text of the attribute.
    EValidity         valid = EValidity::Valid; ///<If during layout we have found this constraint to cause a conflict we invalidate and ignore it.
    mutable int       start_row = -1;   ///<Each constraint results in one or more GLPK rows. This is the first of them. Used to remove this constraint from GLPK.
    mutable int       weaker_than_me=-1;///<If a lower prio constraint is weaker than us, we mark it here. -1 if no such thing.
    mutable std::vector<unsigned> conflicts;///<Number of alternative constraints (strictly before us) thay we may conflict with. Calculated in Solvers

    Constraint() noexcept = default;
    Constraint(const Constraint &) = default;
    Constraint(Constraint &&) noexcept = default;
    Constraint &operator=(const Constraint&) = default;
    bool operator<(const Constraint &c) const;
    /** Constructor just to set the block. */
    Constraint(const BlockBlock* b) noexcept : block(b) {}
    /** Constructor for equality or inequality of two sides with a line where it comes from.*/
    Constraint(const BlockBlock *b, std::string &&s, const FileLineCol &l, EAlignPrio p,
               EType t, const Side &_s1, const Side &_s2) noexcept :
        type(t), prio(p), s1(_s1), s2(_s2),
        block(b), attr_pos(l), display_prio(p), attr_text(std::move(s))
    {
    }
    /** True if this is a min or max Constraint.*/
    bool IsVec() const noexcept { return type==VEC_MAX_EQUAL || type==VEC_MIN_EQUAL; }
    /** Prints a constraint (for debug only)*/
    std::string Print(const std::vector<std::string> &variables);
    /** Generate an error if this constraint is found to cause a conflict.
     * @returns the location where we generated the error, invalid if none.*/
    FileLineCol GenerateError(MscError &error, EAlignPrio report_level);
    /** Generate an error if this constraint is found to cause a conflict for
     * another constraint.
     * @param error The place to report the error to.
     * @param [in] l The location of the other constraint.*/
    void GenerateErrorForAlternative(MscError &error, const FileLineCol &l);
    /** Compares us (first constraint) with another one (second constraint).
     * We dont check priorities here, the caller should call higherprio.Relation(lowerprio)
     * and do something with Stronger or Equivalent.*/
    ERelation Relation(const Constraint &o) const;
};

constexpr Constraint::Side SSide(unsigned v, double offset = 0) noexcept { return Constraint::Side(v, 0, 1, 0, offset); }
constexpr Constraint::Side SSide(const VariableOffset &vo) noexcept { return SSide(vo.var, vo.offset); }
constexpr Constraint::Side SumSide(unsigned v1, unsigned v2, double offset = 0) noexcept { return Constraint::Side(v1, v2, 1, 1, offset); }
constexpr Constraint::Side WSumSide(unsigned v1, unsigned v2, double w1) noexcept { return Constraint::Side(v1, v2, w1, 1-w1, 0); }
constexpr Constraint::Side WSumSide(unsigned v1, unsigned v2, double w1, double w2, double o = 0) noexcept { return Constraint::Side(v1, v2, w1, w2, o); }


class ConstraintSet {
protected:
    std::vector<Variable>    Variables;   ///<GLPK variables, indexed from 1 (index 0 is a Variable{})
    std::vector<Constraint>  Constraints; ///<The constraints we derive from attributes & else
public:
    ConstraintSet() { Variables.emplace_back("", nullptr); } //to match 1-based index
    unsigned AddVariable(const BlockBlock *, std::string_view type);
    void AddConstraint(Constraint &&c) { _ASSERT(c.block); Constraints.push_back(std::move(c)); }
    /** Add a constraint to make s1 equal than s2.*/
     void AddConstrEqual(const BlockBlock *b, std::string &&s, const FileLineCol &l, EAlignPrio p,
                         const Constraint::Side &_s1, const Constraint::Side &_s2)
                         { AddConstraint(Constraint(b, std::move(s), l, p, Constraint::EType::EQUAL, _s1, _s2)); }

    /** Add a constraint to make s1 equal than s2.*/
     void AddConstrEqual(const BlockBlock *b, std::string &&s, EAlignPrio p,
                         const Constraint::Side &_s1, const Constraint::Side &_s2)
                         { AddConstraint(Constraint(b, std::move(s), FileLineCol(), p, Constraint::EType::EQUAL, _s1, _s2)); }


    /** Add a constraint to make s1 greater than s2.*/
     void AddConstrGreater(const BlockBlock *b, std::string &&s, const FileLineCol &l, EAlignPrio p,
                           const Constraint::Side &_s1, const Constraint::Side &_s2)
                         { AddConstraint(Constraint(b, std::move(s), l, p, Constraint::EType::INEQUAL, _s1, _s2)); }

    /** Add a constraint to make s1 greater than s2.*/
     void AddConstrGreater(const BlockBlock *b, std::string &&s, EAlignPrio p,
                           const Constraint::Side &_s1, const Constraint::Side &_s2)
                         { AddConstraint(Constraint(b, std::move(s), FileLineCol(), p, Constraint::EType::INEQUAL, _s1, _s2)); }

    /** Add a constraint to make s1 greater than or equal s2, depending on 'equal'.*/
     void AddConstrGrEq(const BlockBlock *b, std::string &&s, EAlignPrio p, bool equal,
                        const Constraint::Side &_s1, const Constraint::Side &_s2)
                         { AddConstraint(Constraint(b, std::move(s), FileLineCol(), p, equal ? Constraint::EType::EQUAL : Constraint::EType::INEQUAL, _s1, _s2)); }

    /** Add a constraint to make v1 the minimum of variables in 'vec'*/
     void AddConstrMinOf(const BlockBlock *b, unsigned _v1, std::vector<VariableOffset> &&vec, std::string &&s = {}, const FileLineCol &l = {}, EAlignPrio p= EAlignPrio::Must)
	 {
		 if (vec.size()==0) return;
		 if (vec.size()==1)
			 return AddConstrEqual(b, std::move(s), l, p, Constraint::Side(_v1, 0, 1, 0, 0), Constraint::Side(vec.front().var, 0, 1, 0, vec.front().offset));
		 Constraint c(b, std::move(s), l, p, Constraint::EType::VEC_MIN_EQUAL, SSide(_v1), {});
		 c.vec.swap(vec);
		 AddConstraint(std::move(c));
	 }

    /** Add a constraint to make v1 the maximum of veriables in 'vec'*/
     void AddConstrMaxOf(const BlockBlock *b, unsigned _v1, std::vector<VariableOffset> &&vec, std::string &&s = {}, const FileLineCol &l = {}, EAlignPrio p = EAlignPrio::Must)
	 {
		 if (vec.size()==0) return;
		 if (vec.size()==1)
			 return AddConstrEqual(b, std::move(s), l, p, Constraint::Side(_v1, 0, 1, 0, 0), Constraint::Side(vec.front().var, 0, 1, 0, vec.front().offset));
		 Constraint c(b, std::move(s), l, p, Constraint::EType::VEC_MAX_EQUAL, SSide(_v1), {});
		 c.vec.swap(vec);
		 AddConstraint(std::move(c));
	 }

    /** Add a constraint to minimize the difference of two variables.*/
     void AddConstrMinimize(const BlockBlock *b, unsigned _v1, unsigned _v2, EAlignPrio p = EAlignPrio::Must)
                         { AddConstraint(Constraint(b, {}, {}, p, Constraint::EType::DIFF_MIN, SSide(_v1), SSide(_v2))); }
};


enum class ELayoutContentFinalizePass
{
    BLOCK,
    JOIN
};

enum class ECloneContentAdjustment
{
    ADD,
    MOVE,
    DROP,
    UPDATE,
    REPLACE,
};


struct CloneAction;
using CloneActionList = UPtrList<CloneAction>;

struct CloneAction
{
    const ECloneContentAdjustment     action;
    FileLineColRange                  action_pos;
    std::unique_ptr<AttributeList>    attr;
    bool                              recursive = false;
    std::unique_ptr<BlockInstrList>   instr;
    StringWithPosList                 blocks;
    std::string                       before;
    FileLineColRange                  before_pos;
    std::unique_ptr<CloneActionList>  modifiers;
    explicit CloneAction(const FileLineColRange &l, gsl::owner<BlockInstruction *> inst,
                         std::string_view bef = {}, const FileLineColRange bef_pos = {}) :
        action(ECloneContentAdjustment::ADD), action_pos(l), instr(std::make_unique<BlockInstrList>()),
        before(bef), before_pos(bef_pos) {instr->Append(inst);}
    explicit CloneAction(const FileLineColRange &l, gsl::owner<BlockInstrList *> inst,
                         std::string_view bef = {}, const FileLineColRange bef_pos = {}) :
        action(ECloneContentAdjustment::ADD), action_pos(l), instr(inst), before(bef), before_pos(bef_pos) {}
    explicit CloneAction(const FileLineColRange &l, gsl::owner<StringWithPosList*> instrs,
                         std::string_view bef, const FileLineColRange &bef_pos = {}) :
        action(ECloneContentAdjustment::MOVE), action_pos(l), before(bef), before_pos(bef_pos)
        {if (instrs) blocks.swap(*instrs); delete instrs;}
    explicit CloneAction(const FileLineColRange &l, std::string_view blk, const FileLineColRange &blk_pos,
                         gsl::owner<BlockInstruction *> inst) :
        action(ECloneContentAdjustment::REPLACE), action_pos(l), instr(std::make_unique<BlockInstrList>()),
        blocks({{blk, blk_pos}}) {instr->Append(inst);}
    explicit CloneAction(const FileLineColRange &l, std::string_view blk, const FileLineColRange &blk_pos,
                         gsl::owner<BlockInstrList *> inst) :
        action(ECloneContentAdjustment::REPLACE), action_pos(l), instr(inst), blocks({{blk, blk_pos}}) {}
    explicit CloneAction(const FileLineColRange &l, gsl::owner<StringWithPosList*> instrs) :
        action(ECloneContentAdjustment::DROP), action_pos(l) { if (instrs) blocks.swap(*instrs); delete instrs; }
    explicit CloneAction(const FileLineColRange &l, gsl::owner<StringWithPosList*> instrs, const FileLineColRange &/*inst_pos*/,
                         gsl::owner<AttributeList*> a, gsl::owner<CloneActionList *>mod) :
        action(ECloneContentAdjustment::UPDATE), action_pos(l), attr(a), modifiers(mod) {if (instrs) blocks.swap(*instrs); delete instrs;}

    CloneAction(CloneAction &&) = default;
};

class GLPKSolver;

/** Blocks, Arrows and Commands that can be part of a list of instructions
 * kept beyond parsing.*/
class BlockInstruction : public BlockElement
{
public:
    typedef struct AddConstraintResultOneDim
    {
        bool justify_with_neighbour = false; ///<True if we stick to a neighbour and the parent has content_align=justify
        double max_to_imargin = 0;           ///<The maximum coordinate used by arrows relative to the the inner min margin of the parent
        double max_to_visible = 0;           ///<The maximum coordinate used by arrows relative to the min outer edge of the parent
        AddConstraintResultOneDim & operator +=(const AddConstraintResultOneDim &o) {
            justify_with_neighbour |= o.justify_with_neighbour;
            max_to_imargin = std::max(max_to_imargin, o.max_to_imargin);
            max_to_visible = std::max(max_to_visible, o.max_to_visible);
            return *this;
        }
    } AddConstraintResultOneDim;
    typedef struct AddConstraintResult : public std::array<AddConstraintResultOneDim, 2>
    {
        AddConstraintResult & operator +=(const AddConstraintResult &o)
        {
            this->front() += o.front(); this->at(1) += o[1]; return *this;
        }
    } AddConstraintResult;

    using BlockElement::BlockElement;
    BlockInstruction(const BlockInstruction &o, const FileLineColRange &l, std::string_view prefix) : BlockElement(o, l, prefix) {}
    BlockInstruction(const BlockInstruction &) = default;
    BlockInstruction(BlockInstruction &&) = default;
    virtual BlockInstruction *Clone(const FileLineColRange &l, std::string_view prefix,
                                    gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                                    const MultiBlock *multi_parent) const = 0;
    /** Call this to apply the running style (appropriate parts) to this instruction.
     * @param [in] cloning True if this is applied during cloning.
     * @param [in] recursive If true, blocks will call this for all their content.*/
    virtual void ApplyRunningStyle(bool cloning, bool recursive) = 0;
    /** Shall this block be considered for 'prev' or 'next'. */
    virtual bool IsApplicableAsPrevNext() const { return false; }
    /** Increase our level together with that of our children. Used when we or a parent is added as content.*/
    virtual void IncLevel() { }
    /** Do we act as content for a block, requiring our parent to accomodate us in it size?*/
    virtual bool AmIContent() const { return false; }
    virtual Contour GetContourForArrows(const std::vector<XY> &/*except*/, bool /*skip_allow_arrows*/,
                                        const StringWithPosList &/*except_blocks*/,
                                        const StringWithPosList &/*except_parents*/,
                                        double /*distance*/,
                                        const StringWithPosNumberList &/*distance_per_block*/,
                                        double /*lw2*/) const { return {}; }
    virtual bool IsMyChild(const std::unordered_set<const BlockBlock*> &/*except*/) const { return false; }
    /** When we or a parent of us is made a template, here can we unregister any drawing
    * or spacing.*/
    virtual void ResolveAlignmentAttributes(const BlockBlock */*first*/, const BlockBlock */*prev*/, const BlockBlock */*next*/, const BlockBlock */*last*/) { }
    virtual void AddVariableIndices(ConstraintSet&, unsigned /*from*/, unsigned /*to*/) const {}
    virtual AddConstraintResult
        AddConstraints(Canvas&, ConstraintSet&, unsigned /*from*/, unsigned /*to*/,
                       unsigned /*parent_index_justify*/[2], const InternalAlignment */*parent_attr_justify*/[2]) const
        { return {}; }
    virtual bool CompareLayouts(ConstraintSet&, unsigned /*from*/, unsigned /*to*/) { return false; }
    virtual void LayoutFromVariables(GLPKSolver &/*constraints*/, unsigned /*from*/, unsigned /*to*/) { }
    virtual bool LayoutWithGLPK(Canvas&, unsigned /*from*/, unsigned /*to*/, size_t /*progress_units*/) { return false; }
    virtual void ShiftToZeroIfLaidOut(unsigned /*from*/, unsigned /*to*/) noexcept { }
    virtual void LayoutFinalizeBlocks(Canvas&, ELayoutContentFinalizePass) {}
};

/** A class to store join commands*/
class JoinCommand : public BlockInstruction
{
    StringWithPosList blocks;         ///<We join these blocks
    mutable Contour   combined_area;  ///<Which result in this combined area
    JoinCommand(const JoinCommand &o, const FileLineColRange &l, std::string_view prefix);
public:
    JoinCommand(BlockChart &ch, const FileLineColRange &l, StringWithPosList &&blks);
    static void AttributeNames(Csh &);
    static bool AttributeValues(std::string_view attr, Csh &csh);
    void AddAttributeList(gsl::owner<AttributeList*>a) override;
    BlockInstruction *Clone(const FileLineColRange &l, std::string_view prefix,
                            gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                            const MultiBlock* multi_parent) const override;
    void ApplyRunningStyle(bool /*cloning*/, bool /*recursive*/) override {}
    bool PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number) override;
    void LayoutFinalizeBlocks(Canvas & canvas, ELayoutContentFinalizePass p) override;
    void Draw(Canvas &c) const override;
};

/** Represents changes to the numbering levels after popping a context.
 * If the number of levels outside a context is less than inside, then
 * after popping the context, we shall truncate the list of current numbers
 * and increment the resulting last number, so that after 1.2.2,
 * the next element is 1.3 and not 1.2.
 * This object can only be generated by MscChart::PopContext().
 * It is done, if the outer context has less levels of numbering than the inner.*/
class SetNumbering : public BlockInstruction
{
public:
    const size_t set_length; ///<If we change the length of the number list in PostParseProcess(), change it to this long (shall be a trimming, never an extension). Zero if no cange
    const size_t inc_by;     ///<In PostParseProcess() increment the number by this much. Zero if no increment.

    SetNumbering(BlockChart &chart, size_t len, size_t inc) :
        BlockInstruction(chart, FileLineColRange()),
        set_length(len), inc_by(inc) {}
    BlockInstruction *Clone(const FileLineColRange &l, std::string_view prefix,
                            gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                            const MultiBlock* multi_parent) const override;
    void ApplyRunningStyle(bool /*cloning*/, bool /*recursive*/) override {}
    bool PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number) override;
};

/** A block of a blockchart. May contain other blocks.
 * Its processing steps are the following.
 * - In the constructor, we store the name given by the user in 'display_name' and
 *   calculate a unique name with the linenumber into 'name'.
 *   We also calculate what would be the 'style.child_alignment' attributes if the element
 *   contained content. We store it in 'chart.parent_align'.
 *   We also clear the style.
 * - In AddAttributeList() we add any attribute to style (all other values will remain unset.
 *   We also update 'chart.parent_align' if the user has set 'child.*'.
 * - In ApplyPre() we may add any alignment modifiers, if the user specified one (or two).
 * - In SetContent(), we add any content and calculate the final 'style'.
 * - In PostParseProcess() we calculate numbering and process the label escapes.
 *     We also sanitize our alignment attribute values
 * - In FinalizeLabel() we resolve \ r text escapes.
 * - In ResolveAlignmentAttributes() we replace 'prev' and similar in alignment attributes.
 * - In AddVariableIndices() we create the GLPK variables needed to describe us.
 * - In AddConstraints() we create all the GLPK constraints describing our layout.
 * - In LayoutFromVariables() we assume the layout has been solved and all variables have good values.
 *     Here we take these values and fill into 'outer_line' and 'label_block'.
 * - In PostPosProcess() we create our coverage area and register us for tracking.
 * - In Draw() we draw ourselves.*/
class BlockBlock : public BlockInstruction
{
protected:
    bool names_registered = false;      ///<Flag if we haves our names registered, so we when we unregister templates, they are not attempted to be registered
    BlockBlock(BlockChart &chart, EBlockType t, EBlockType c_t, int sh, const FileLineColRange &l, std::string_view name);
    BlockBlock(const BlockBlock &o, const FileLineColRange &l, std::string_view name, std::string_view prefix, bool copy_content=true);
    static AlignmentAttr *
        TranslatePre(BlockChart &chart, AlignModifier c1, AlignModifier c2,
                     const FileLineCol &l1, const FileLineCol &l2,
                     const StringWithPosList *xblocks, const StringWithPosList *yblocks,
                     EdgePos xmin, EdgePos xmax, EdgePos ymin, EdgePos ymax,
					 bool keep_margin_received,
                     bool no_offset_for_minor, bool no_offset_for_center);
    std::optional<Contour>
        GetContourForArrowsHelper(const std::vector<XY> &except, bool skip_allow_arrows,
                                  const StringWithPosList& except_blocks,
                                  const StringWithPosList& except_parents,
                                  double distance,
                                  const StringWithPosNumberList& distance_per_block,
                                  double lw2) const;

public:
    struct Port
    {
        std::array<EdgePos, 2> xy;
        double dir; ///<Compass point: degree from noon clockwise. -1: no dir, -2: perpendicular to closest point on contour
    };
    const EBlockType   type;            ///<Our original type created with
    const EBlockType   content_type;    ///<Same as type, except for multi-blocks which have the type of their content here
    const int          shape;           ///<If we are a shape, our number. Else -1.
    const std::string  name_original;   ///<The name of the block as specified by the user. May be empty, if the user added no name.
    const std::string  name_full;       ///<The name of the block with its prefixes added. Empty if the user specified no name for this block (and any of the parents). Ends in a dot if we have no name, but our parent has. See FormChildName().
    const std::string  name_unique;     ///<The name of the block. We append the line number to make it unique.
          std::string  name_display;    ///<The human-readable name of the block. Only has line numbers for disambiguation, never empty. We disambiguate in PostParseProcess(), when we have all blocks added.
          std::string  name_gui_collapse;///<Use this name when identifying us towards the GUI for the purpose of collapse/expand. This can be the name of another entity (if we are the front element of a multi).

    bool               track = true;    ///<If set to false, we do not add ourselves to the tracking rectangles (a child of multi)
    unsigned           level = 0;       ///<Our depth in the block hierarchy. Top level main node is 0.
    unsigned           rank = 0;        ///<The position among our parent's children. Used to break alignment attr ties. Set in SetContent() or ApplyCloneModifiers() of the parent
    BlockInstrList     content;         ///<The content of the block, as it shows (if any). For collapsed blocks this is either empty or holds an indicator.
    BlockInstrList     collapsed_content;///<The content of the block, if collapsed.
    StringWithPosList  around;          ///<We should be around these blocks
    bool orig_has_visible_content = false;///<True if we have any blocks within 'content' or 'around'. False if only other instructions (arrows, joins, etc.) are there. Set in SetContent().
    bool      has_visible_content = false;///<True if we have any blocks within 'content' or 'around' *and* we are not collapsed. It can also be true if we are collapsed but show an indicator.
    OptAttr<bool>      collapsed;       ///<The `collapsed=` attribute if specified either in source or via the GUI. Reset from 'orig_collapsed' on a clone (GUI commands not cloned.)
    OptAttrLine<bool>  orig_collapsed;  ///<The `collapsed=` attribute if specified in source. `file_pos` contains the location of the attribute (name) in the input file.
    const BlockBlock*  hidden_by = nullptr; ///<The topmost ancestor of us that is collapsed. If set, we are not shown.
    std::optional<BlockStyle> alternate_style; ///<Set only if we have visible content and the indicator is false. If we are not collapsed, this contains the style for the collapsed us (and vice versa). Used when we are cloned with a different collapse status.
    const StyleCoW<BlockStyle> indicator_style;///<The "indicator" style captured at the point of creation
    const StyleCoW<BlockStyle> running_style;  ///<The block running style captured at the point of creation

    mutable unsigned index_out_min[2] = {};     ///<The GLPK variable index for left and top outer_line ignoring margin
    mutable unsigned index_out_max[2] = {};     ///<The GLPK variable index for right and bottom outer_line ignoring margin
    mutable unsigned index_in_min[2] = {};      ///<The GLPK variable index for left and top inner line plus imargin. For shapes it is the place specified by the T element (imargin ignored). Differs from index_out_min with linewith and the shape of the block, plus the imargin.
    mutable unsigned index_in_max[2] = {};      ///<The GLPK variable index for right and bottom inner line minus imargin. For shapes it is the place specified by the T element (imargin ignored). Differs from index_out_min with linewith and the shape of the block, plus the imargin.
    mutable unsigned index_content_min[2] = {}; ///<The GLPK variable index for left/top of content inside. Valid only if we have content blocks.
    mutable unsigned index_content_max[2] = {}; ///<The GLPK variable index for right/bottom of content inside. Valid only if we have content blocks.
    mutable unsigned index_label_min[2] = {};   ///The GLPK variable index for left top of label inside. Valid only if we have both content and label. If we only have label, we do things separately.
    mutable unsigned index_label_max[2] = {};   ///The GLPK variable index for right/bottom of label inside. Valid only if we have both content and label. If we only have label, we do things separately.
    mutable unsigned index_justify[2] = {};     ///<If our content is justified, this is the variable for the spacing

    mutable Block outer_line;       ///<The outer bounding box of the block
    mutable Block inner_line;       ///<The inner margin of the block
    mutable XY available_for_label; ///<The label size, we use (may be scaled if size_mode is not 'at_least')

    explicit BlockBlock(BlockChart &chart);
    /** Constructor to initialize a row, column, box or space - any block other than a shape.*/
    BlockBlock(BlockChart &chart, EBlockType t, const FileLineColRange &l, std::string_view name = {})
        : BlockBlock(chart, t, t, -1, l, name) {}
    /** The constructor to initialize a shape*/
    BlockBlock(BlockChart &chart, int sh, const FileLineColRange &l, std::string_view name = {})
        : BlockBlock(chart, EBlockType::Shape, EBlockType::Shape, sh, l, name) {}

    /** Compares two blocks for resolving ties in alignment attrs. If we are in the same list, our rank determines.
     * Else our position in the file. Normally the two are the same but for cloned blocks, content can be rearranged,
     * so elements later defined in the file may come before others, so we need the rank mechanism.
     * Attributes of blocks *earlier* in the list/file have higher precedence,
     * hence this "less" function returns true if our rank is higher.*/
    bool less_for_alignment_ties(const BlockBlock &o) const {
        return parent==o.parent ? rank>o.rank : file_pos.start>o.file_pos.start;
    }

    void AddToDrawOrder(const BlockElement *blk = nullptr, bool before = true) override
        { return AddToDrawOrderWithContent(content, blk, before); }
    void AddToDrawOrder(const StringWithPosList &list, bool before = true) override
        { return AddToDrawOrderWithContent(content, list, before); }

    /** The base style of the block. (not virtual, called from constr)*/
    const BlockStyle *BaseStyle(bool hasContent) const;
    /** The enhancement style of the block. (not virtual, called from constr)*/
    const BlockStyle *EnhancementStyle() const;
    /** The style to apply to the content of the block. */
    std::string_view ContentStyleName() const;
    /** The style to apply to the content of the block. */
    const BlockStyle *ContentStyle() const;
    /** Shall this block be considered for 'prev' or 'next'. */
    bool IsApplicableAsPrevNext() const override { return around.size()==0; }
    /** Can we be multi'ed?*/
    virtual bool CanBeMulti() const { return true; }
    /** Returns empty if can be collapsed, else an error to display for the attr. */
    virtual std::string_view CanBeCollapsed() const noexcept { return is_any_of(content_type, EBlockType::Box, EBlockType::Boxcol, EBlockType::Shape) ? "" : "Only visible blocks can be collapsed."; }
    /** Get the location of a port.*/
    std::optional<Port> GetPort(std::string_view name) const;
    BlockBlock *FindApplicableNextAtOrAfter(BlockInstrList::const_iterator i) const;
    BlockBlock *FindApplicablePrevBefore(BlockInstrList::const_iterator i) const;
    bool AddAttribute(const Attribute & a) override;
    /** Add the attribute names for block type `t` we take to `csh`.*/
    static void AttributeNames(EBlockType t, Csh &);
    /** Add a list of possible attribute value names for block type `t` to `csh` for attribute `attr`.*/
    static bool AttributeValues(EBlockType t, std::string_view /*attr*/, Csh &/*csh*/);
    /** Increase our level together with that of our children. Used when we or a parent is added as content.*/
    void IncLevel() override { ++level; for (auto &p:content) p->IncLevel(); }
    bool AmIContent() const override { return true; }
    void RegisterNames();
    void UnRegisterNames();
    virtual void SetContent(std::variant<nullptr_t, BlockInstrList*, StringWithPos> c);
    void MakeTemplate();
    void AdjustMultiAttributes(unsigned seq, const XY &offset);
    /** Translate one 'below' 'left', etc. modifiers to an AlignmentAttr.*/
    static AlignmentAttr *TranslatePre(BlockChart &chart,
                                       AlignModifier c, const FileLineCol &l,
                                       gsl::owner<StringWithPosList *> blocks = nullptr)
        { return TranslatePre(chart, c, c, l, l, blocks); }
    static AlignmentAttr *TranslatePre(BlockChart &chart,
                                       AlignModifier c1, AlignModifier c2,
                                       const FileLineCol &l1, const FileLineCol &l2,
                                       gsl::owner<StringWithPosList *> blocks = nullptr);
    /** Translates one 'below' 'left', etc. modifiers from a coordinate to an AlignmentAttr. */
    static AlignmentAttr *TranslatePre(BlockChart &chart,
                                       AlignModifier c, const FileLineCol &l,
                                       std::string_view coord, const FileLineCol &lc)
         { return TranslatePre(chart, c, c, l, l, coord, lc); }
    static AlignmentAttr *TranslatePre(BlockChart &chart,
                                       AlignModifier c1, AlignModifier c2,
                                       const FileLineCol &l1, const FileLineCol &l2,
                                       std::string_view coord, const FileLineCol &lc);
    void ApplyPre(gsl::owner<AlignmentAttr *>local_alignment);
	virtual void ApplyFurtherAttributes(const AttributeList *al);
    void AddAround(gsl::owner<StringWithPosList*> blocks, const FileLineCol &l);
    virtual void UpdateCollapsedFromGUI();
    void ApplyCloneModifiers(gsl::owner<CloneActionList *> mod);
    BlockBlock *Clone(const FileLineColRange &l, std::string_view prefix,
					  gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                      const MultiBlock* multi_parent) const override
        { return CloneAs(l, name_original, prefix, nullptr, attr, mod, multi_parent); }
    virtual BlockBlock *CloneAs(const FileLineColRange &l, std::string_view name, std::string_view prefix,
								gsl::owner<AlignmentAttr*> align,
								gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                                const MultiBlock* multi_parent) const;
    void ApplyRunningStyle(bool cloning, bool recursive) override;
    BlockBlock *GetCommonParent(const BlockBlock *other);
    const BlockBlock *GetCommonParent(const BlockBlock *other) const;
    std::unordered_set<const BlockBlock*> GetMyParents(const BlockBlock *p = nullptr) const;
    double GetDistanceForMe(double distance, const StringWithPosNumberList &distance_per_block) const;
    Contour GetContourForArrows(const std::vector<XY> &except, bool skip_allow_arrows,
                                const StringWithPosList &except_blocks,
                                const StringWithPosList &except_parents,
                                double distance,
                                const StringWithPosNumberList &distance_per_block, 
                                double lw2) const override;
    Contour GetVisibleCover() const;
    bool IsMyParent(const BlockBlock *p) const { return p && (p==parent || (parent && parent->IsMyParent(p))); }
    bool IsMyChild(const std::unordered_set<const BlockBlock*> &except) const override;
    void KillAlignmentBasedOnChildAttr(bool y, EAlignPrio p);
    bool PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number) override;
    void FinalizeLabel(Canvas &canvas) override;
    void ResolveAlignmentAttributes(const BlockBlock *first, const BlockBlock *prev, const BlockBlock *next, const BlockBlock *last) override;
    void AddVariableIndices(ConstraintSet &constraints, unsigned from, unsigned to) const override;

    std::pair<unsigned, unsigned> AddMinMaxConstraint(ConstraintSet &constraints, const StringWithPosList &list, double offset,
                                                      bool margin, bool y, bool do_min, bool do_max) const;
    bool FillTargetSide(ConstraintSet &constraints, Constraint &c, const OptAlignAttr &a, bool y) const;
    std::pair<double, double> GetInOutDiff(bool y) const;
    /** Returns the size of our label as laid out, taking orientation (but not scaling due to size_mode) into account.*/
    XY GetLabelSize() const
    {
        XY ret = parsed_label.getTextWidthHeight();
        if (IsVertical(style.label_orient.value_or(EOrientation::Normal)))
            ret.SwapXY();
        return ret;
    }
    AddConstraintResult
        AddConstraints(Canvas & canvas, ConstraintSet &constraints, unsigned from, unsigned to,
                       unsigned parent_index_justify[2], const InternalAlignment *parent_attr_justify[2]) const override;
    void LayoutFromVariables(GLPKSolver &constraints, unsigned from, unsigned to) override;
    bool LayoutWithGLPK(Canvas &canvas, unsigned from, unsigned to, size_t progress_units) override;
    void LayoutFinalizeBlocks(Canvas & canvas, ELayoutContentFinalizePass p) override;
    void ShiftBy(const XY &xy) override { outer_line.Shift(xy); inner_line.Shift(xy); BlockElement::ShiftBy(xy); for (auto &pBlock : content) pBlock->ShiftBy(xy); }
    virtual double GetPerpendicularDir(const EdgePos &x, const EdgePos &y) const;
    void PostPosProcess(Canvas &, Chart *) override;
    void Draw(Canvas &canvas) const override;
    ~BlockBlock() override;
};

/** A block resulting from a "space" command.*/
class BlockSpace : public BlockBlock
{
protected:
    BlockSpace(const BlockSpace &o, const FileLineColRange &l, std::string_view prefix) :
        BlockBlock(o, l, {}, prefix), size(o.size) {}
public:
    const double size;            ///<Our size as specified by the user.
    BlockSpace(BlockChart &ch, const FileLineColRange &l, double s = -1) :
        BlockBlock(ch, EBlockType::Space, l), size(s) {}
    BlockBlock *CloneAs(const FileLineColRange &l, std::string_view name, std::string_view prefix,
						gsl::owner<AlignmentAttr*> align,
						gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                        const MultiBlock* multi_parent) const override;

    bool CanBeMulti() const override { return false; }
    std::string_view CanBeCollapsed() const noexcept override { return "Spaces cannot be collapsed."; }
    bool PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number) override;
};

/** A block resulting from a "break" command.*/
class BlockBreak : public BlockBlock
{
protected:
    BlockBreak(const BlockBreak &o, const FileLineColRange &l, std::string_view prefix) :
        BlockBlock(o, l, {}, prefix) {}
public:
    BlockBreak(BlockChart &ch, const FileLineColRange &l) :
        BlockBlock(ch, EBlockType::Space, l) {}
    BlockBlock *CloneAs(const FileLineColRange &l, std::string_view name, std::string_view prefix,
						gsl::owner<AlignmentAttr*> align,
						gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                        const MultiBlock* multi_parent) const override;
    bool CanBeMulti() const override { _ASSERT(0); /*parser should not get here*/ return false; }
    std::string_view CanBeCollapsed() const noexcept override { return "Breaks cannot be collapsed."; }
    bool PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number) override;
};

/** A block showing as an indicator. Used only internally.*/
class BlockIndicator : public BlockBlock {
public:
    BlockIndicator(BlockChart& ch, const FileLineColRange& l) :
        BlockBlock(ch, EBlockType::Indicator, l) {}
    BlockBlock* CloneAs(const FileLineColRange&, std::string_view /*name*/, std::string_view /*prefix*/,
                        gsl::owner<AlignmentAttr*>,
                        gsl::owner<AttributeList*>, gsl::owner<CloneActionList*>,
                        const MultiBlock* /*multi_parent*/) const override { _ASSERT(0); return nullptr; }
    bool CanBeMulti() const override { _ASSERT(0); /*parser should not get here*/ return false; }
    std::string_view CanBeCollapsed() const noexcept override { return "Indicators cannot be collapsed."; }
    void SetContent(std::variant<nullptr_t, BlockInstrList*, StringWithPos> c) override;
    bool PostParseProcess(Canvas& canvas, const BlockBlock* hide_by, Numbering& number) override;
    void Draw(Canvas& canvas) const override;
};

class BlockInlinedChart : public BlockBlock
{
protected:
    BlockInlinedChart(const BlockInlinedChart& o, const FileLineColRange& l, std::string_view name, std::string_view prefix) :
        BlockBlock(o, l, name, prefix), lang(o.lang), text(o.text) {
    }
public:
    std::string   lang;          ///<The language name if symbol_type==INLINE
    std::string   text;          ///<The text of the inlined chart
    InliningData  inlining_data; ///<The compiled chart and its position


    BlockInlinedChart(BlockChart& ch, std::string_view l, const FileLineColRange& pos, std::string_view name = {}) :
        BlockBlock(ch, EBlockType::Chart, pos, name), lang(l) {
    }
    BlockBlock* CloneAs(const FileLineColRange& l, std::string_view name, std::string_view prefix,
                        gsl::owner<AlignmentAttr*> align,
                        gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList*> mod,
                        const MultiBlock* multi_parent) const override;

    bool CanBeMulti() const override { return true; }
    std::string_view CanBeCollapsed() const noexcept { return "Inlined charts cannot be collapsed."; }
    void SetContent(std::variant<nullptr_t, BlockInstrList*, StringWithPos> c) override;
    bool PostParseProcess(Canvas& canvas, const BlockBlock* hide_by, Numbering& number) override;
    void PostPosProcess(Canvas&, Chart*) override;
    void Draw(Canvas& canvas) const override;
    const InliningData* GetInlinedChartData() const override { return inlining_data.chart ? &inlining_data : nullptr; }
    void InlinedChartGUIStateChange() const override;
};

class MultiBlock : public BlockBlock
{
    std::unique_ptr<BlockBlock> original_block; ///<The block, originally used to create us.
protected:
    MultiBlock(const MultiBlock &o, const FileLineColRange &l,
               std::string_view name, std::string_view prefix) :
        BlockBlock(o, l, name, prefix, /*copy_content=*/false), count(o.count) {}

public:
    const unsigned count; ///<How many copies to show
    /** Constructor to initialize a row, column, box or space - any block other than a shape.*/
    MultiBlock(int num, gsl::owner<BlockBlock *> b, const FileLineColRange &l);
    static int CategorizeAttribute(const Attribute& a);
    static AttributeList* CopyAttributes4Children(const AttributeList* al, bool front);
	void ApplyFurtherAttributes(const AttributeList *al) override;
	BlockBlock *CloneAs(const FileLineColRange &l, std::string_view name, std::string_view prefix,
						gsl::owner<AlignmentAttr*> align,
						gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                        const MultiBlock* multi_parent) const override;
    void ApplyRunningStyle(bool cloning, bool recursive) override;
    bool CanBeMulti() const override { _ASSERT(0); /*parser should not get here*/ return false; }
    void UpdateCollapsedFromGUI() override {} //Special handling of this in MultiBlock::SetContent()
    void SetContent(std::variant<nullptr_t, BlockInstrList*, StringWithPos> c) override;
	bool PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number) override;
	void LayoutFinalizeBlocks(Canvas & canvas, ELayoutContentFinalizePass p) override;
};


/** A structure used to parse a copy command.
 * Must be a POD as it will be part of a bison union.*/
struct CopyParseHelper
{
    FileLineColRange file_pos; ///<The position of the header
    multi_segment_string block_name; ///<The block we copy (may be null if it is omitted - ignore command then)
    FileLineCol block_pos;     ///<The location of the name of the to-be copied block.
    const BlockBlock *block;   ///<The block we copy. We do not own this pointer.
    multi_segment_string copy_name; ///<The name of the copy (if any)
    AlignmentAttr *align;      ///<Any alignment modifiers
    AttributeList *attributes; ///<Updated/added attributes of the copy (if any)
    CloneActionList *modifiers;///<Updated/added content of the copy (if any)
    /** Initialize to all nulls.*/
    void init() { file_pos.MakeInvalid(); block_name.init(); block_pos.MakeInvalid(); block = nullptr; copy_name.init(); align = nullptr;  attributes = nullptr; modifiers = nullptr; }
    /** Free non-null members */
    void destroy() { block_name.destroy(); copy_name.destroy(); delete align;  delete attributes; delete modifiers; init(); }
};

inline unsigned ConstraintSet::AddVariable(const BlockBlock *b, std::string_view type) {
    Variables.emplace_back(StrCat(b->name_unique, '.', type), b, Variable::Mapping{(unsigned)Variables.size()});
    return (unsigned)Variables.size()-1;
}

} //namespace
#endif //BLOCK_BLOCKS_H