/*
    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.cpp The definition for Blocks
* @ingroup libblock_files */

/** @defgroup libblock_files Files for the block library.
* @ingroup libblock*/


#include <ranges>
#include <glpk.h>
#include "canvas.h"
#include "blockchart.h"
#include "blockcsh.h"
#include "parser_tools.h"

using namespace block;

std::string block::FormChildName(std::string_view parent, std::string_view name) {
    if (parent.empty()) return std::string{ name };
    if (parent.back() == '.') {
        if (name.empty()) return std::string{ parent };
        return StrCat(parent, name);
    }
    if (name.empty()) return StrCat(parent, '.');
    return StrCat(parent, '.', name);
}


std::string Constraint::Side::Print(const std::vector<std::string> &Variables)
{
    std::string ret;
    if (w1!=1 && w1!=0) ret.append(std::to_string(w1)).push_back('*');
    if (w1) ret.append(Variables[v1]);
    if (w1 && w2) ret.append(" + ");
    if (w2!=1 && w2!=0) ret.append(std::to_string(w2)).push_back('*');
    if (w2) ret.append(Variables[v2]);
    if ((w1 || w2) && offset) ret.append(" + ").append(std::to_string(offset));
    if (ret.length()==0)
        ret = "EMPTY!!";
    return ret;
}

std::string Constraint::Print(const std::vector<std::string> &Variables)
{
    if (IsVec()) {
        _ASSERT(s1.w1==1 && s1.w2==0); //only one variable
        _ASSERT(s1.offset==0);
        std::string s2 = type==VEC_MIN_EQUAL ? "MIN OF" : "MAX OF";
        for (auto &v : vec) {
            s2.push_back(' ');
            s2.append(Variables[v.var]);
            if (v.offset>0) {
                s2.push_back('+');
                s2.append(std::to_string(v.offset));
            } else if (v.offset<0)
                s2.append(std::to_string(v.offset)); //includes - sign
        }
        return Variables[s1.v1]+"=="+s2;
    } else {
        switch (type) {
        default: _ASSERT(0); FALLTHROUGH;
        case EQUAL:   return s1.Print(Variables) +"==" +s2.Print(Variables);
        case INEQUAL: return s1.Print(Variables) +">=" +s2.Print(Variables);
        case DIFF_MIN: return "minimize "+s1.Print(Variables)+"-"+s2.Print(Variables);
        }
    }
}

/** We will use this to sort Constraints.
* A constraint is more important if has higher prio. In case of ties
* a constraint for a block deeper in the hierarchy is more important.
* In case they are at the same level, then
* - if they are in the same list (same parent) their position in that list counts
*   (even if they have later been inserted into that position during cloning).
* - if they have different parents, their position in the input file decides.*/
bool Constraint::operator<(const Constraint &c) const
{
    if (prio<c.prio) return true;
    if (prio==c.prio && block && c.block) {
        if (block->level<c.block->level) return true;
        if (block->level==c.block->level)
            return block->less_for_alignment_ties(*c.block);
    }
    return false;
}



FileLineCol Constraint::GenerateError(MscError &error, EAlignPrio report_level)
{
    FileLineCol p1, p2;
    std::string msg1, msg2;
    const std::string astr(PriorityText(display_prio));
    const std::string an_astr = astr[0]=='a' ? "an " : "a ";
    if (justify_attr_pos.IsValid()) {
        _ASSERT(block);
        p1 = attr_pos.IsValid() ? attr_pos : block ? block->file_pos.start : FileLineCol();
        msg1 = "This block cannot be justified because of conflicts with other alignment attributes.";
        p2 = justify_attr_pos;
        msg2 = "This is the place of the justify attribute ignored.";
    } else if (attr_pos.IsInvalid()) {
        p1 = block ? block->file_pos.start : FileLineCol();
        msg1 = "This "+astr+" '"+attr_text+"' is causing a conflict. Removing it.";
    } else if (display_prio==EAlignPrio::Explicit) {
        p1 = attr_pos;
        msg1 = "This "+astr+" caused a conflict. Ignoring it.";
    } else if (block) {
        p1 = block->file_pos.start;
        msg1 = "This block has "+an_astr+astr+" causing conflict. Ignoring it.";
        p2 = attr_pos;
        msg2 = "This is where the "+astr+" was defined.";
    } else {
        _ASSERT(0);
        p1 = attr_pos;
        msg1 = "This "+astr+" causes a conflict somewhere.";
    }
    if (display_prio>=EAlignPrio::Explicit) {
        error.Error(p1, msg1);
        if (msg2.length()) error.Error(p2, p1, msg2);
    } else if (display_prio>=report_level) {
        error.Warning(p1, msg1);
        if (msg2.length()) error.Warning(p2, p1, msg2);
    } //else silently drop
    return p1;
}

void Constraint::GenerateErrorForAlternative(MscError &error, const FileLineCol &l)
{
    FileLineCol p1;
    std::string msg1;
    const std::string astr(PriorityText(display_prio));
    const std::string an_astr = astr[0]=='a' ? "an " : "a ";
    if (justify_attr_pos.IsValid()) {
        _ASSERT(block);
        p1 = attr_pos.IsValid() ? attr_pos : block ? block->file_pos.start : FileLineCol();
        msg1 = "block's justfy attribute";
    } else if (attr_pos.IsInvalid()) {
        p1 = block ? block->file_pos.start : FileLineCol();
        msg1 = StrCat(astr, " '", attr_text, '\'');
    } else if (display_prio==EAlignPrio::Explicit) {
        p1 = attr_pos;
        msg1 = astr;
    } else if (block) {
        p1 = block->file_pos.start;
        msg1 = StrCat("block has ", an_astr, astr);
    } else {
        _ASSERT(0);
        p1 = attr_pos;
        msg1 = astr;
    }
    error.Error(p1, l, StrCat("The conflict may be with this ", msg1, '.'));
}


Constraint::ERelation Constraint::Relation(const Constraint & o) const
{
    //Currently we only handle the case when both constraints are
    //inequal or when I am equal and the other is inequal
	if (type == EType::EQUAL && o.type == EType::INEQUAL) {
		if (s1.VariablesEqual(o.s1) && s2.VariablesEqual(o.s2))
			//OK, I am equal the other is inequal.
			//If my offset is stricter or the same as that of 'o', I am stronger
			//Note, o is INEQUAL and thus says: o.s1>=o.s2
			return s1.offset-s2.offset <= o.s1.offset-o.s2.offset ? ERelation::Stronger : ERelation::Unrelated;
		if (s1.VariablesEqual(o.s2) && s2.VariablesEqual(o.s1))
			//OK, I am equal the other is inequal.
			//If my offset is stricter or the same as that of 'o', I am stronger
			//Note, o is INEQUAL and thus says: o.s1>=o.s2
			return s2.offset-s1.offset <= o.s1.offset-o.s2.offset ? ERelation::Stronger : ERelation::Unrelated;
	} else if (type == EType::INEQUAL && o.type == EType::EQUAL) {
			if (s1.VariablesEqual(o.s1) && s2.VariablesEqual(o.s2))
				//OK, I am inequal the other is equal.
				//If his offset is stricter or the same as that of mine, I am weaker
				//Note, o is INEQUAL and thus says: o.s1>=o.s2
				return s1.offset-s2.offset >= o.s1.offset-o.s2.offset ? ERelation::Weaker : ERelation::Unrelated;
			if (s1.VariablesEqual(o.s2) && s2.VariablesEqual(o.s1))
				return s2.offset-s1.offset >= o.s1.offset-o.s2.offset ? ERelation::Weaker : ERelation::Unrelated;
	} else if (type == EType::INEQUAL && o.type == EType::INEQUAL) {
        if (s1.VariablesEqual(o.s1) && s2.VariablesEqual(o.s2)) {
            //OK, I am inequal the other is inequal, too.
            //If my offset is stricter or the same as that of 'o', I am stronger
            //Note, o is INEQUAL and thus says: o.s1>=o.s2
            if (s1.offset-s2.offset == o.s1.offset-o.s2.offset)
                return ERelation::Equivalent;
            return s1.offset-s2.offset <= o.s1.offset-o.s2.offset ? ERelation::Stronger : ERelation::Weaker;
        }
    } else if (type == EType::EQUAL && o.type == EType::EQUAL) {
        //Two equivalence constraints. If both variables and offset are equal, we are
        //equivalent.
        if ((s1.offset-s2.offset == o.s1.offset-o.s2.offset &&
                 s1.VariablesEqual(o.s1) && s2.VariablesEqual(o.s2)) ||
            (s1.offset-s2.offset == o.s2.offset-o.s1.offset &&
                  s1.VariablesEqual(o.s2) && s2.VariablesEqual(o.s1)))
            return ERelation::Equivalent;
    }
    return ERelation::Unrelated;
}


/*************************************************************************
 * BlockElement
 *************************************************************************/

/** The constructor to initialize the main block of the diagram.
 * Note that "chart" may not be fully initialized here.
 * We do not take styles nor do we add ourselves to the drawing order.*/
BlockElement::BlockElement(BlockChart &ch) :
    chart(ch), concrete_number(-1)
{
}

BlockElement::BlockElement(BlockChart &ch, const FileLineColRange &l) :
    chart(ch), name_prefix(chart.GetCurrentPrefix()),
    concrete_number(-1),
    numberingStyle(ch.MyCurrentContext().numberingStyle) //comes from the current one, not the one copied from
{
    file_pos = l;
    AddToDrawOrder();
    chart.Progress.RegisterBulk(ESections::POSTPROCESS, 2); //One for PostParseProcess, one for FinalizeLabels
    chart.Progress.RegisterBulk(ESections::POSTPOS, 1);
    chart.Progress.RegisterBulk(ESections::DRAW, 1);
}

/** This is used to create a clone */
BlockElement::BlockElement(const BlockElement &o, const FileLineColRange&, std::string_view prefix) :
    Element(o), valid(o.valid), chart(o.chart),
    name_prefix(prefix), style(o.style), parsed_label(o.parsed_label),
    basic_format(o.basic_format), concrete_number(o.concrete_number),
    numberingStyle(o.numberingStyle), number_text(o.number_text), wildcard_repl(o.wildcard_repl),
    label_block(o.label_block)
{
    AddToDrawOrder();
    chart.Progress.RegisterBulk(ESections::POSTPROCESS, 2); //One for PostParseProcess, one for FinalizeLabels
    chart.Progress.RegisterBulk(ESections::POSTPOS, 1);
    chart.Progress.RegisterBulk(ESections::DRAW, 1);
}

void BlockElement::AddToDrawOrder(const BlockElement *blk, bool before)
{
	if (blk == this) return;
    RemoveFromDrawOrder();
    chart.DrawOrder.insert(FindInDrawOrder(blk, before), this);
}


void BlockElement::AddToDrawOrder(const StringWithPosList &list, bool before)
{
	auto i = FindInDrawOrder(list, before);
	if (*i==this) return;
    RemoveFromDrawOrder();
    chart.DrawOrder.insert(i, this);
}

void BlockElement::AddToDrawOrderWithContent(const BlockInstrList &content, const BlockElement *blk, bool before)
{
    //Here we need to move all the content, too
    BlockElement::AddToDrawOrder(blk, before);
    if (!before) blk = this;
    for (auto &pInstr : content) {
        pInstr->AddToDrawOrder(blk, before);
        if (!before) blk = pInstr.get();
    }
}

void BlockElement::AddToDrawOrderWithContent(const BlockInstrList &content, const StringWithPosList &list, bool before)
{
    //Here we need to move all the content, too
    BlockElement::AddToDrawOrder(list, before);
    const BlockElement *blk = this;
    for (auto &pInstr : content) {
        pInstr->AddToDrawOrder(blk, false);
        blk = pInstr.get();
    }
}




void BlockElement::RemoveFromDrawOrder() const
{
    remove(chart.DrawOrder, this);
}

std::vector<BlockElement*>::iterator BlockElement::FindInDrawOrder(const BlockElement *blk, bool before)
{
    std::vector<BlockElement*>::iterator i = chart.DrawOrder.end();
    if (blk) {
        i = std::find(chart.DrawOrder.begin(), chart.DrawOrder.end(), blk);
        if (!before && i!=chart.DrawOrder.end())
            ++i;
    }
    return i;
}

std::vector<BlockElement*>::iterator BlockElement::FindInDrawOrder(const StringWithPosList &list, bool before)
{
    std::vector<BlockElement*>::iterator i = before ? chart.DrawOrder.begin() : chart.DrawOrder.end();
    for (; i != (before ? chart.DrawOrder.end() : chart.DrawOrder.begin()); before ? i++ : i--) {
        auto p = dynamic_cast<BlockBlock*>(before ? *i : *std::prev(i));
        if (p && list.end() != std::find_if(list.begin(), list.end(),
                                            [this, p](auto &sp)
                                            {return chart.GetBlockByName(sp.name, parent, true, true)[0]==p; })) {
            //We have found the first (if 'before') or last (if '!before)
            return i; //for 'before==false' we track the one after anyway.
        }
    }
    return chart.DrawOrder.end();
}

bool BlockElement::AddAttribute(const Attribute & a)
{
    if (style.AddAttribute(a, &chart)) return true;
    if (style.f_numbering && a.Is("number")) {
        if (a.type == EAttrType::NUMBER) {
            if (a.number >= 0) {
                concrete_number = int(a.number);
                style.numbering = true;
            } else
                chart.Error.Error(a, true, "Value for 'number' must not be negative. Ignoring attribute.");
            return true;
        }
        if (a.type == EAttrType::BOOL) {
            style.numbering = a.yes;
            return true;
        }
        if (a.type == EAttrType::CLEAR) { //turn off numbering
            style.numbering = false;
            return true;
        }
        //We have a string as number - it may be a roman number or abc
        int num;
        unsigned off = chart.MyCurrentContext().numberingStyle.Last().Input(a.value, num);
        //off is how many characters we could not understand at the end of a.value
        if (off == a.value.length()) {
            //No characters understood
            chart.Error.Error(a, true, "Value for 'number' must be 'yes', 'no' or a number. Ignoring attribute.");
            return true;
        }
        if (off > 0) {
            FileLineCol l(a.linenum_value.start);
            l.col += (unsigned)a.value.length() - off;
            chart.Error.Warning(l, "I could not understand number from here. Applying only '" +
                                 a.value.substr(0, a.value.length() - off) + "'.");
        }
        concrete_number = num;
        style.numbering = true;
        return true;
    }
    return false;
}

void BlockElement::AttributeNames(Csh &csh, bool number)
{
    if (number)
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"number",
                               "Enable (yes) or disable (no) numbering, or set a specific number.",
                               EHintType::ATTR_NAME));
}

bool BlockElement::AttributeValues(std::string_view attr, Csh &csh, bool number)
{
    if (number && CaseInsensitiveEqual(attr, "number")) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE)+"yes",
                               "Turn auto-numbering on. (May already be turned on via a style or chart option.)",
                               EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, CshHintGraphicParam(1)));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE)+"no",
                               "Turn auto-numbering off. (May already be turned off via a style, chart option or by default.)",
                               EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, CshHintGraphicParam(0)));
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number>",
                               "Specify a number to use for this element. Auto-numbering will continue from this value.",
                               EHintType::ATTR_VALUE, false));
        return true;
    }
    return false;
}


void BlockElement::AddAttributeList(gsl::owner<AttributeList*> al)
{
    if (al) {
        for (auto &attr : *al)
            if (!AddAttribute(*attr))
                chart.Error.Error(*attr, false, "Unrecognized or inapplicable attribute. Ignoring it.");
        delete al;
    }
    //Here we need to keep a label set to zero length as explicitly set, so that we can
    //override the default label from the running style with the zero length label.
    if (style.label && style.label->length() &&
        StringFormat::RemovePosEscapesCopy(*style.label).length()==0)
        style.label = { {},{} };
}

/** Apply formatting to our label and increase the numbering if we had a number.
 * @param canvas The canvas we will be drawn to.
 * @param [in] hide_by The topmost ancestor that is collapsed. If set, we will not eventually show.
 * @param number The current running number.
 * @returns true if all is OK. False if an error is found and the element shall be removed.*/
bool BlockElement::PostParseProcess(Canvas &canvas [[maybe_unused]], const BlockBlock* hide_by [[maybe_unused]], Numbering &number)
{
    if (!valid) return false;
	if (style.draw_time.size()) {
		Resolve(style.draw_time, { nullptr, nullptr, nullptr, nullptr, parent }, {}, {},
				"Use of '%b' in the 'draw_before' or 'draw_after' attribute is not allowed. Ignoring block.", nullptr, nullptr);
		CheckExists(style.draw_time, "block", { HandleHidden::Remove });
		if (style.draw_time.size()==0)
			chart.Error.Error(file_pos.start, "No valid blocks listed in 'draw_before' or 'draw_time' attribute. Ignoring it.");
	}
	for (auto &sp: style.draw_time) {
        const BlockBlock *bl = chart.GetBlockByNameWithError(sp.name, sp.file_pos.start, parent, "attribute");
        if (bl)
            AddToDrawOrder(bl, style.draw_time_is_before);
    }
    //Here the value of 'label' is final. If empty, mark it as not set.
    if (style.label && style.label->empty())
        style.label.reset();

    //We do everything here even if we are hidden (numbering is not impacted by hide/show or collapse/expand)
    if (style.label) {
        basic_format = style.text;
        if (*style.numbering) {
            number.SetSize((unsigned)numberingStyle.Size()); //append 1s if style has more levels
            if (concrete_number >= 0)
                number.Last() = concrete_number;
            number_text = numberingStyle.Print(number);
            //Now remove escapes from the number text (if any)
            //At this point the number text must be processed using StringFormat::ExpandReferences
            //to expand remaining empty \c(), \s(), etc escapes.
            //We use a dummy linenum, as we should not get ANY errors here...
            StringFormat::ExpandReferences(number_text, &chart, FileLineCol(), &basic_format,
                                           false, true, StringFormat::LABEL, true, wildcard_repl);
            number.increment(numberingStyle.Last().increment);
        }
        basic_format.Apply(std::string_view(*style.label));
        if (style.label_orient == EOrientation::Left)
            label_rotate = 90;
        else if (style.label_orient == EOrientation::Right)
            label_rotate = 270;
        else if (style.label_orient == EOrientation::UpsideDown)
            label_rotate = 180;
    }
    chart.Progress.DoneBulk(ESections::POSTPROCESS);
    return true;
}

void BlockElement::FinalizeLabel(Canvas &canvas)
{
    chart.Progress.DoneBulk(ESections::POSTPROCESS);
    if (!style.label || style.label->empty()) return;
    string pre_num_post;
    if (*style.numbering)
        pre_num_post = *numberingStyle.pre + number_text + *numberingStyle.post;
    //We add empty num and pre_num_post if numbering is turned off, to remove \N escapes
    StringFormat::AddNumbering(*style.label, number_text, pre_num_post);
    //Next we add reference numbers to labels, and also kill off any \s or the like
    //escapes that came with numbers in pre_num_post
    //We can start with a dummy pos, since the label's pos is prepended
    //during ProcessLabel. Note that with this calling
    //(references parameter true), ExpandReferences will only emit errors
    //to missing references - all other errors were already emitted in
    //the call in PostParseProcess()
    StringFormat::ExpandReferences(*style.label, &chart, FileLineCol(),
        &basic_format, true, true, StringFormat::LABEL, true, wildcard_repl);
    parsed_label.Set(*style.label, canvas, chart.Shapes, style.text);
}

void BlockElement::PostPosProcess(Canvas &canvas, Chart *ch)
{
    Element::PostPosProcess(canvas, ch);
    chart.Progress.DoneBulk(ESections::POSTPOS);
}

void BlockElement::CollectIsMapElements(Canvas &canvas)
{
    if (!style.f_text || !style.label || style.label->empty() || parsed_label.IsEmpty())
        return;
    if (label_rotate ==0 || label_rotate == 180) //we don't rotate upside-down as Chart::RegisterLabel() has no such override
        parsed_label.CollectIsMapElements(chart.ismapData, canvas, chart.Shapes,
                                            label_block.x.from, label_block.x.till, label_block.y.from);
    else if (label_rotate == 90) //EOrientation::Left
        parsed_label.CollectIsMapElements(chart.ismapData, canvas, chart.Shapes,
                                            label_block.y.from, label_block.y.till, label_block.x.from, ESide::RIGHT);
    else if (label_rotate == 270) //EOrientation::Right
        parsed_label.CollectIsMapElements(chart.ismapData, canvas, chart.Shapes,
                                            label_block.y.from, label_block.y.till, label_block.x.till, ESide::LEFT);
    else 
        parsed_label.CollectIsMapElements(chart.ismapData, canvas, chart.Shapes,
                                            label_block.x.from, label_block.x.till, label_block.y.from, label_block.x.MidPoint(),
                                            label_block.Centroid(), label_rotate);
}

void BlockElement::RegisterLabels()
{
    if (!style.f_text || !style.label || style.label->empty() || parsed_label.IsEmpty())
        return;
    if (label_rotate == 0 || label_rotate == 90 || label_rotate == 180 || label_rotate == 270)
        chart.RegisterLabel(parsed_label, LabelInfo::BOX, label_block);
    else
        chart.RegisterLabel(parsed_label, LabelInfo::BOX, 
                            label_block.x.from, label_block.x.till, label_block.y.from, label_block.x.MidPoint(),
                            label_block.Centroid(), label_rotate);
}

Contour BlockElement::LabelCover() const
{
    if (!style.f_text || !style.label || style.label->empty() || parsed_label.IsEmpty())
        return {};
    if (label_rotate == 0)
        return parsed_label.Cover(chart.Shapes, label_block.x.from, label_block.x.till, label_block.y.from);
    if (label_rotate == 90) //EOrientation::Left
        return parsed_label.Cover(chart.Shapes, label_block.y.from, label_block.y.till, label_block.x.from, ESide::RIGHT);
    if (label_rotate == 270) //EOrientation::Right
        return parsed_label.Cover(chart.Shapes, label_block.y.from, label_block.y.till, label_block.x.till, ESide::LEFT);
    return parsed_label.Cover(chart.Shapes, label_block.x.from, label_block.x.till, label_block.y.from).
        RotateAround(label_block.Centroid(), label_rotate);
}

void BlockElement::DrawLabel(Canvas &canvas, bool even_if_delayed) const
{
    if (delay_label_drawing && !even_if_delayed) return;
    if (!style.f_text || !style.label || style.label->empty() || parsed_label.IsEmpty())
        return;
    if (label_rotate == 0)
        return parsed_label.Draw(canvas, chart.Shapes, label_block.x.from, label_block.x.till, label_block.y.from);
    if (label_rotate == 90) //EOrientation::Left
        return parsed_label.Draw(canvas, chart.Shapes, label_block.y.from, label_block.y.till, label_block.x.from, ESide::RIGHT);
    if (label_rotate == 270) //EOrientation::Right
        return parsed_label.Draw(canvas, chart.Shapes, label_block.y.from, label_block.y.till, label_block.x.till, ESide::LEFT);
    canvas.Transform_Rotate(label_block.Centroid(), M_PI * label_rotate / 180);
    parsed_label.Draw(canvas, chart.Shapes, label_block.x.from, label_block.x.till, label_block.y.from);
    canvas.UnTransform();
}

const std::vector<std::string> BlockElement::spec_block_names = {
    "first", "prev", "next", "last", STRING_PARENT
};



/*************************************************************************
* Commands
*************************************************************************/


/** The constructor to construct a clone. */
JoinCommand::JoinCommand(const JoinCommand &o, const FileLineColRange &l, std::string_view prefix) :
    BlockInstruction(o, l, prefix), blocks(o.blocks)
{
    //push all locations
    FileLineCol inclusion(l.start.TopOfStack());
    for (auto &b : blocks)
        b.file_pos.Push(inclusion, EInclusionReason::COPY);
}

/** The constructor to construct a regular join. */
JoinCommand::JoinCommand(BlockChart &ch, const FileLineColRange &l,
                         StringWithPosList &&blks) :
    BlockInstruction(ch, l), blocks(std::move(blks))
{
    style = chart.MyCurrentContext().styles["join"].read();
    style.type = EStyleType::ELEMENT;
    style.block_style_type = EBlockStyleType::Unspecified;
    style.Empty();
}


BlockInstruction *JoinCommand::Clone(const FileLineColRange &l, std::string_view prefix,
                                     gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                                     const MultiBlock* multi_parent [[maybe_unused]]) const {
    _ASSERT(multi_parent == nullptr);
    JoinCommand *ret = new JoinCommand(*this, file_pos.CreatePushed(l, EInclusionReason::COPY), prefix);
    ret->BlockInstruction::AddAttributeList(attr);
    if (mod) {
        if (mod->size())
            chart.Error.Error(mod->front()->action_pos.start, "Join commands do not accept content modifiers. Ignoring them.");
        delete mod;
    }
    return ret;
}


void JoinCommand::AttributeNames(Csh &csh)
{
    csh.AttributeNamesForStyle("join");
    BlockInstruction::AttributeNames(csh, false);
}

bool JoinCommand::AttributeValues(std::string_view attr, Csh &csh)
{
    if (csh.AttributeValuesForStyle(std::string(attr), "join"))
        return true;
    return BlockInstruction::AttributeValues(attr, csh, false);
}


void JoinCommand::AddAttributeList(gsl::owner<AttributeList*>a)
{
    //Now create our style
    style = chart.MyCurrentContext().styles["join"].read();
    style.type = EStyleType::STYLE; //Temporarily make us 'style' so that user can clear any element.
    style.block_style_type = EBlockStyleType::Generic;
    //Take the line style from the running style for blocks
    style.line += chart.MyCurrentContext().running_style_blocks.read().line;
    //Add the attributes, we have collected
    //Note: no enhancement style, nothing from the global text.* and nothing else from the running style.
	BlockInstruction::AddAttributeList(a); //Now the attributes are added to in 'style'
	style.type = EStyleType::ELEMENT; //OK back to element.I am not sure it will be used later on.

    //OK, kill the attributes of the joined entities that are set with us
    if (style.f_line && style.line.type)
        style.line.MakeComplete();
    else
        style.f_line = false;
    if (style.f_fill && style.fill.color)
        style.fill.MakeComplete();
    else
        style.f_fill = false;
    if (style.f_shadow && style.shadow.offset)
        style.shadow.MakeComplete();
    else
        style.f_shadow = false;
}

bool JoinCommand::PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number)
{
    Resolve(blocks, {nullptr, nullptr, nullptr, nullptr, parent}, {}, {},
            "Use of '%b' in the 'join' clause is not allowed. Ignoring block.", nullptr, nullptr);
    CheckExists(blocks, "block", { HandleHidden::Keep });
    if (blocks.empty()) { //user listed no valid blocks
        chart.Error.Error(file_pos.start, "No valid blocks listed in 'join' clause. Ignoring entire command.");
        chart.Progress.DoneBulk(ESections::POSTPROCESS);
        return false;
    }
    //See if some blocks are hidden
    auto original_blocks = blocks;
    if (CheckExists(blocks, "block", { HandleHidden::Remove })) { //all blocks hidden - not an error, just drop us.
        chart.Progress.DoneBulk(ESections::POSTPROCESS);
        return false;
    }
    if (blocks.size()!=original_blocks.size()) { //some blocks hidden, some not - report to user
        blocks = std::move(original_blocks);
        CheckExists(blocks, "block", { HandleHidden::Remove, "This block is hidden because block '%B' is collapsed. Ignoring from 'join' command."});
    }
    for (auto &blk: blocks) {
        BlockBlock *const b = const_cast<BlockBlock *>(chart.GetBlockByName(blk.name, parent, true, true)[0]);
        if (style.f_line) b->style.line.type = ELineType::NONE; //keep corner and radius for correct layout
        if (style.f_fill) {
            b->style.fill = FillAttr::None();
            b->delay_label_drawing = true; //Don't draw labels with the joined blocks, our fill would cover them
        }
        if (style.f_shadow) b->style.shadow = ShadowAttr::None();
        
    }
    return BlockInstruction::PostParseProcess(canvas, hide_by, number); //Call to sort out draw_after and draw_before
}

void JoinCommand::LayoutFinalizeBlocks(Canvas&, ELayoutContentFinalizePass p)
{
    if (p!=ELayoutContentFinalizePass::JOIN) return;
    for (auto &blk: blocks)
         combined_area += chart.GetBlockByName(blk.name, parent, true, true)[0]->GetAreaToDraw();
    area = combined_area;
}

void JoinCommand::Draw(Canvas &canvas) const
{
    if (canvas.does_graphics()) {
        if (style.f_shadow && !style.shadow.IsNone())
            canvas.Shadow(combined_area, style.shadow);
        if (style.f_fill && !style.fill.IsFullyTransparent())
            canvas.Fill(combined_area, style.fill);
        if (style.f_line && !style.line.IsFullyTransparent())
            canvas.Line(combined_area, style.line);
    } else
        canvas.Add(GSShape(combined_area,
                           style.f_line ? style.line : LineAttr::None(),
                           style.f_fill ? style.fill : FillAttr::None(),
                           style.f_shadow ? style.shadow : ShadowAttr::None()));
    //If we have fill, we have delayed the drawing of labels: do them now
    if (style.f_fill) 
        for (auto& blk : blocks)
            chart.GetBlockByName(blk.name, parent, true, true)[0]->DrawLabel(canvas, true);
}


BlockInstruction *SetNumbering::Clone(const FileLineColRange&, std::string_view prefix [[maybe_unused]],
                                      gsl::owner<AttributeList*> attr [[maybe_unused]], gsl::owner<CloneActionList *> mod [[maybe_unused]],
                                      const MultiBlock* multi_parent [[maybe_unused]]) const
{
    _ASSERT(!attr && !mod);
    _ASSERT(!multi_parent);
    return new SetNumbering(chart, set_length, inc_by);
}

/** This Instruction only sets the running number. Returns false and gets removed.*/
bool SetNumbering::PostParseProcess(Canvas&, const BlockBlock* hide_by [[maybe_unused]], Numbering &number)
{
    if (set_length)
        number.SetSize(unsigned(set_length));
    if (inc_by)
        number.increment(int(inc_by));
    chart.Progress.DoneBulk(ESections::POSTPROCESS);
    return false;
}





/*************************************************************************
 * BlockBlock
 *************************************************************************/


/** The constructor to initialize the main block of the diagram.
 * Note that "chart" may not be fully initialized here. */
BlockBlock::BlockBlock(BlockChart &chart) :
    BlockInstruction(chart),
    type(EBlockType::Row), content_type(EBlockType::Row), shape(-1),
    name_unique("(MAIN)"), level(0)
    //we leave full name and original name empty
{
    style.type = EStyleType::ELEMENT;
    style.block_style_type = EBlockStyleType::Unspecified;
    track = false;
}

BlockBlock::BlockBlock(BlockChart & c, EBlockType t, EBlockType c_t, int sh, const FileLineColRange & l, std::string_view n) :
	BlockInstruction(c, l),
    type(t), content_type(c_t), shape(sh),
    name_original(n),
    name_full(FormChildName(chart.GetCurrentPrefix(), name_original)),
    name_unique(chart.CreateUniqueName(name_original, l.start)),
    level(0),
    indicator_style(chart.GetCurrentContext() ? chart.MyCurrentContext().styles["indicator"] : StyleCoW<BlockStyle>()),
    running_style(chart.GetCurrentContext() ? chart.MyCurrentContext().running_style_blocks : StyleCoW<BlockStyle>())
{
    for (auto s:{"prev", "next", "first", "last"})
        if (s==name_original) {
            chart.Error.Error(file_pos.start, "You cannot use 'first', 'last', 'next' and 'prev' as block names. Ignoring block.");
            valid = false;
        }
    if (name_original.find('.')!=std::string::npos) {
        chart.Error.Error(file_pos.start, "Block names cannot contain dot ('.') characters. Ignoring block.");
        valid = false;
    }
    wildcard_repl = name_original;
    _ASSERT(!l.IsInvalid());
    style = *BaseStyle(true);
    style.type = EStyleType::ELEMENT;
    style.block_style_type = EBlockStyleType::Unspecified;
    style.Empty(); // collect attributes here and merge with default style in SetContent()

    //Set chart.parent_align
    const auto s = ContentStyle();
    if (s) {
        chart.parent_style = *s;
        chart.parent_style.type = EStyleType::STYLE;
        chart.parent_style.block_style_type = EBlockStyleType::Content;
        chart.parent_style.alignment.SetAllPriority(EAlignPrio::Content);
    } else
        chart.parent_style.Empty();
    chart.current_parent = this;
    chart.Progress.RegisterBulk(ESections::POSTPROCESS, 1);   //One extra task for resolve attributes
}


/** This constructor is used to create a clone. */
BlockBlock::BlockBlock(const BlockBlock &o, const FileLineColRange &l,
                       std::string_view name, std::string_view prefix, bool copy_content) :
    BlockInstruction(o, l, prefix),
    type(o.type), content_type(o.content_type), shape(o.shape),
    name_original(name),
    name_full(FormChildName(prefix, name_original)),
    name_unique(chart.CreateUniqueName(name_original, l.start)),
    track(o.track), level(0), around(o.around),
    orig_has_visible_content(o.orig_has_visible_content), has_visible_content(o.has_visible_content),
    collapsed(o.orig_collapsed), orig_collapsed(o.orig_collapsed), //'collapsed' initialized from 'o.orig_collapsed': any GUI collapse/expand commands are dropped when cloning
    indicator_style(o.indicator_style), running_style(o.running_style)
{
    _ASSERT(!l.IsInvalid());
    for (auto s:{"prev", "next", "first", "last"})
        if (s==name_original) {
            chart.Error.Error(file_pos.start, "You cannot use 'first', 'last', 'next' and 'prev' as block names. Ignoring block.");
            valid = false;
        }
    if (name_original.find('.')!=std::string::npos) {
        chart.Error.Error(file_pos.start, "Block names cannot contain dot ('.') characters. Ignoring block.");
        valid = false;
    }
    wildcard_repl = name_original;
    file_pos = l;
    /* leave style uninitialized! (well, copied from o) */
    //clone original content specified by user
    if (copy_content) {
        const FileLineColRange inclusion(l.TopOfStack());
        for (auto& pBlock : o.collapsed.value_or(false) ? o.collapsed_content : o.content) {
            auto p = pBlock->Clone(inclusion, name_full.size() ? name_full : prefix, nullptr, nullptr, nullptr);
            p->SetParent(this);
            p->IncLevel();
            content.Append(p);
        }
    }
    chart.Progress.RegisterBulk(ESections::POSTPROCESS, 1);   //One extra task for resolve attributes
}


BlockBlock::~BlockBlock()
{
    UnRegisterNames();
}

const BlockStyle * BlockBlock::BaseStyle(bool hasContent) const
{
    const std::string_view n = [this, hasContent]() {
        switch (type) {
        default: _ASSERT(0); FALLTHROUGH;
        case block::EBlockType::Box:
        case block::EBlockType::Boxcol:
            return hasContent ? "container" : "block";
		case block::EBlockType::Shape:
			return hasContent ? "container_shape" : "block";
		case block::EBlockType::Text:
            return "text";
        case block::EBlockType::Row:
        case block::EBlockType::Column:
        case block::EBlockType::Cell:
        case block::EBlockType::Space:
            return "invis";
        case block::EBlockType::Indicator:
            return "indicator";
        case block::EBlockType::Chart:
            return "inlined_chart";
        }
    }();
    const auto i = chart.MyCurrentContext().styles.find(std::string(n));
    return i==chart.MyCurrentContext().styles.end() ? nullptr : &i->second.read();
}

const BlockStyle * BlockBlock::EnhancementStyle() const
{
    const std::string n = [this]()->std::string {
        switch (type) {
        case block::EBlockType::Box:
        case block::EBlockType::Boxcol:
            return "shape_box";
        case block::EBlockType::Shape:
            return "shape_"+chart.Shapes.GetShape(shape)->name;
        default:
        case block::EBlockType::Row:
        case block::EBlockType::Column:
        case block::EBlockType::Cell:
            return {};
        }
    }();
    if (n.length()==0) return nullptr;
    const auto i = chart.MyCurrentContext().styles.find(n);
    return i==chart.MyCurrentContext().styles.end() ? nullptr : &i->second.read();
}

std::string_view BlockBlock::ContentStyleName() const
{
    switch (content_type) {
    default:
        return {};
    case block::EBlockType::Shape:
    case block::EBlockType::Box:
    case block::EBlockType::Row:
        return "row";
    case block::EBlockType::Boxcol:
    case block::EBlockType::Column:
        return "col";
    }
}

const BlockStyle * BlockBlock::ContentStyle() const
{
    const std::string_view n = ContentStyleName();
    if (n.length()==0) return nullptr;
    const auto i = chart.MyCurrentContext().styles.find(std::string(n));
    return i==chart.MyCurrentContext().styles.end() ? nullptr : &i->second.read();
}


/** Resolves a port of the block to actual positions.
 * This will be called during PostParseProcess(), so we don't have
 * block layout yet.*/
std::optional<BlockBlock::Port>
BlockBlock::GetPort(std::string_view name) const
{
    constexpr double POS_OFF = 0; //Let us leave this precise
	//Each block has a port for each compass point denoting a compass dir
	//(so not for 'perp' and 'center')
	auto dir = chart.ParseCompassPoint(name, FileLineCol(), false);
	if (dir && dir.value() >= 0) {
		//OK, a valid compass point, seek where it crosses the shape
		const Contour cont = shape < 0 ? Contour(0, 1, 0, 1) : chart.Shapes.Cover(shape, Block(0,1,0,1));
		const double radian = (dir.value()-90)*M_PI/180; //-90 because zero is north
		const XY vector = { cos(radian)*2, sin(radian)*2 };
		DoubleMap<bool> map(false);
		cont.Cut(XY(0.5,0.5), XY(0.5, 0.5)+vector, map);
		_ASSERT(map.size());
		if (map.size()>1) {
			_ASSERT(std::prev(map.end())->second==false);
			//last element is the last crosspoint expressed as a 0..1 pos of
			//(0.5;0.5) ->(0.5;0.5)+vector (may be outside 0..1 range )
            //Use a slightly smaller value to make the port a bit inside the shape
			const XY point = XY(0.5, 0.5) + (std::prev(map.end())->first-POS_OFF)*vector;
			return Port{{{{ point.x, false, 0 }, {point.y, false, 0}}}, dir.value()};
		}
		//valid compass point, but no crosspoints found.
		//Return center
		return Port{ {{{ 0.5, false, 0 }, {0.5, false, 0}}}, dir.value() };
	}
    if (shape<0) {
        //Use a slightly smaller value to make the port a bit inside the shape
        if (CaseInsensitiveEqual(name, "top"))
            return Port{{{{ 0.5, false, 0 }, {POS_OFF, false, 0}}}, 0};
        if (CaseInsensitiveEqual(name, "topright"))
            return Port{{{{1-POS_OFF, false, 0},{ POS_OFF, false, 0}}}, 45};
        if (CaseInsensitiveEqual(name, "right"))
            return Port{{{{1-POS_OFF, false, 0},{0.5, false, 0}}}, 90};
        if (CaseInsensitiveEqual(name, "bottomright"))
            return Port{{{{1-POS_OFF, false, 0},{1-POS_OFF, false, 0}}}, 135};
        if (CaseInsensitiveEqual(name, "bottom"))
            return Port{{{{0.5, false, 0},{1-POS_OFF, false, 0}}}, 180};
        if (CaseInsensitiveEqual(name, "bottomleft"))
            return Port{{{{ POS_OFF, false, 0},{1-POS_OFF, false, 0}}}, 225};
        if (CaseInsensitiveEqual(name, "left"))
            return Port{{{{ POS_OFF, false, 0 }, {0.5, false, 0}}}, 270};
        if (CaseInsensitiveEqual(name, "topleft"))
            return Port{{{{ POS_OFF, false, 0},{ POS_OFF, false, 0}}}, 315};
        // Try parsing edges, like: s0.5, e0.5, w0.5, n0.5
        if (const size_t i = std::string_view("nswe").find(name[0]); i!=std::string::npos)
            if (double pos; !from_chars(name.substr(1), pos))
                if (0<=pos && pos<=100)
                    switch (i) {
                    case 0: return Port{{{{pos/100, false}, {0, false}}}, 0};
                    case 1: return Port{{{{pos/100, false}, {1, false}}}, 180};
                    case 2: return Port{{{{0, false}, {pos/100, false}}}, 270};
                    case 3: return Port{{{{1, false}, {pos/100, false}}}, 90};
                    default: _ASSERT(0); break;
                    }
        //fallthrough to end
    } else {
        auto pShape = shape>=0 ? chart.Shapes.GetShape(shape) : nullptr;
        auto pPort = pShape ? pShape->GetPort(std::string(name), Block(0, 1, 0, 1)) : std::optional<Shape::Port>{};
        if (pPort)
            return Port{{ { { pPort.value().xy.x, false, 0 }, {pPort.value().xy.y, false, 0} }}, pPort->dir};
        //fallthrough to end
    }
    return {};
}

/** Find the next element in 'content' at or after 'i' that is eligible as 'next'.
 * @param [in] i the iterator in 'content' to search after. Can be content.end().
 *               If 'i' is applicable, we return it.
 * @returns the block, applicable or nullptr if there is no applicable after 'i'.*/
BlockBlock *BlockBlock::FindApplicableNextAtOrAfter(BlockInstrList::const_iterator i) const
{
    auto iNext = std::find_if(i, content.end(), [](auto &p) {return p->IsApplicableAsPrevNext(); });
    return iNext==content.end() ? nullptr : dynamic_cast<BlockBlock *>(iNext->get());
}

/** Find the previous element in 'content' before 'i' that is eligible as 'prev'.
 * @param [in] i the iterator in 'content' to search *before*. Can be content.end().
 * @returns the block, applicable or nullptr if there is no applicable before 'i'.*/
BlockBlock *BlockBlock::FindApplicablePrevBefore(BlockInstrList::const_iterator i) const
{
    auto iPrev = std::find_if(BlockInstrList::const_reverse_iterator(i), content.rend(), [](auto &p) {return p->IsApplicableAsPrevNext(); });
    return iPrev==content.rend() ? nullptr : dynamic_cast<BlockBlock *>(iPrev->get());
}


bool BlockBlock::AddAttribute(const Attribute & a)
{
    if (a.Is("collapsed")) {
        if (auto s = CanBeCollapsed(); s.size()) {
            chart.Error.Error(a, false, s);
            return true;
        }
        if (!a.CheckType(EAttrType::BOOL, chart.Error)) return true;
        orig_collapsed = { a.yes, a.linenum_attr.start };
        collapsed = a.yes;
        return true;
    }
    return BlockInstruction::AddAttribute(a);
}

void BlockBlock::AttributeNames(EBlockType t, Csh &csh)
{
    bool number = false;
    switch (t) {
    default: _ASSERT(0); FALLTHROUGH;
    case block::EBlockType::Box:
    case block::EBlockType::Boxcol:
    case block::EBlockType::Shape:
        csh.AttributeNamesForStyle("block"); //same attributes as "container"
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"collapsed",
                               "Setting it to 'yes' will skip showing the content of this block.",
                               EHintType::ATTR_NAME));
        number = true;
        break;
    case block::EBlockType::Text:
        csh.AttributeNamesForStyle("text");
        number = true;
        break;
    case block::EBlockType::Chart:
        csh.AttributeNamesForStyle("inlined_chart");
        break;
    case block::EBlockType::Space:
    case block::EBlockType::Row:
    case block::EBlockType::Column:
        //no attributes to tweak the inner workings of a row, column or space
        break;
    }
    BlockInstruction::AttributeNames(csh, number);
}

bool BlockBlock::AttributeValues(EBlockType t, std::string_view attr, Csh &csh)
{
    if (AlignmentAttr::AttributeValues(attr, csh)) return true;
    bool number = false;
    switch (t) {
    default: _ASSERT(0); FALLTHROUGH;
    case block::EBlockType::Box:
    case block::EBlockType::Boxcol:
    case block::EBlockType::Shape:
        if (csh.AttributeValuesForStyle(std::string(attr), "block")) //same attributes as "container"
            return true;
        if (CaseInsensitiveEqual(attr, "collapsed")) {
            csh.AddYesNoToHints();
            return true;
        }
        number = true;
        break;
    case block::EBlockType::Text:
        if (csh.AttributeValuesForStyle(std::string(attr), "text"))
            return true;
        number = true;
        break;
    case block::EBlockType::Chart:
        if (csh.AttributeValuesForStyle(std::string(attr), "inlined_chart"))
            return true;
        number = true;
        break;
    case block::EBlockType::Space:
    case block::EBlockType::Row:
    case block::EBlockType::Column:
        //no attributes to tweak the inner workings of a row, column or space
        break;
    }
    return BlockInstruction::AttributeValues(attr, csh, number);
}

void BlockBlock::RegisterNames()
{
    if (names_registered) return;
    if (name_original.length())
        chart.BlockNames_Original.insert({name_original, this});
    chart.BlockNames_Full.insert({ name_full, this }); //name_full may be empty here, no problem
    const size_t index = chart.BlockNames_Full.count(name_full);
    name_gui_collapse = index <= 1 ? name_full : StrCat(name_full, ":", std::to_string(index));
    chart.BlockNames_Unique.insert({name_unique, this});
    //Display_name is determined in PostParseProcess()
    names_registered = true;
}

void BlockBlock::UnRegisterNames()
{
    if (!names_registered) return;
    const auto unique = chart.BlockNames_Unique.find(name_unique);
    if (unique!=chart.BlockNames_Unique.end()) {
        _ASSERT(unique->second==this);
        chart.BlockNames_Unique.erase(unique);
    }
    const auto original = chart.BlockNames_Original.equal_range(name_original);
    for (auto i = original.first; i!=original.second; /*nope*/)
        if (i->second==this)
            chart.BlockNames_Original.erase(i++);
        else
            i++;

    const auto full = chart.BlockNames_Full.equal_range(name_full);
    for (auto i = full.first; i!=full.second; /*nope*/)
        if (i->second==this)
            chart.BlockNames_Full.erase(i++);
        else
            i++;
    names_registered = false;
}



/** Set the our content to 'c'. Also calculate our style.
 * This is mandatory to be called, even if with nullptr;
 * except for cloned objects, which has their style and content
 * ready - they just need to register their names.
 * @param [in] c The list of blocks to set. Its content will be
 *         removed, but the object itself will not be deleted. May be null.*/
void BlockBlock::SetContent(std::variant<nullptr_t, BlockInstrList*, StringWithPos> c)
{
    //Register our name
    if (valid)
        RegisterNames();
    switch (c.index()) {
    case 1:
        if (BlockInstrList* l = std::get< BlockInstrList*>(c)) {
            chart.MoveArrowLabels(*l); //move arrow labels into their respective arrows
            unsigned rank = 0;
            for (auto& p : *l) {
                BlockBlock* bb = dynamic_cast<BlockBlock*>(p.get());
                if (bb)
                    bb->rank = rank++; //used to break ties. Higher rank, more prio if parents are the same
                p->SetParent(this);
                p->IncLevel();
                has_visible_content |= p->AmIContent();
                content.push_back(std::move(p));
            }
            break;
        }
        FALLTHROUGH;
    default:
    case 2:
        _ASSERT(0);
        FALLTHROUGH;
    case 0:
        break;
    }
    orig_has_visible_content = has_visible_content;
    UpdateCollapsedFromGUI();
    if (has_visible_content && collapsed.value_or(false)) {
        //When a block is collapsed, we move its content to 'collapsed_content'
        //and either has 'content' empty or just contain a single BlockIndicator.
        //This is so that if this block is later cloned - but not collapsed,
        //we can clone the saved content with it.
        collapsed_content.swap(content);
        _ASSERT(content.empty());
        if (style.indicator.value_or(true)) {
            auto p = std::make_unique<BlockIndicator>(chart, file_pos);
            p->rank = 0;
            p->SetParent(this);
            p->SetContent(nullptr); //This requires a parent
            p->IncLevel();
            content.push_back(std::move(p));
        }
        has_visible_content = style.indicator.value_or(true);
    }
    //Now the attributes specified by the user on this block are in 'style'
    BlockStyle attributes = std::move(style);
    style.Empty();
    style = *BaseStyle(has_visible_content);
    style.type = EStyleType::ELEMENT; //mark us as an element
    style.block_style_type = EBlockStyleType::Unspecified;
    //Block style may come from 'container' or 'block' depending on if we have
    //content or not. (A collapsed block having an indicator counts as having
    //content.). We not only calculate our style here (derived from one of the
    //above base styles plus adding enhancement and running styles at this point
    //of definition from the current context), but also the style derived from
    //the other base style. So if we have content, but are collapsed and have no
    //indicator, we derive our style from 'block' and the alternative style from
    //'container'. Likewise, if we have content and are not collapsed, we derive
    //our style from 'container' and the alternative style from 'block'.
    //The alternative style is used when we clone this block, but with a different
    //collapse status. The cloned instance must look like as if it were created
    //here using the base style, enhancement styles and the running style at this
    //point - which will not be available at the point of clone. So here we prepare.
    if (orig_has_visible_content) {
        alternate_style = *BaseStyle(!has_visible_content); //Inverted! This sets a value to 'alternate_style'
        alternate_style->type = EStyleType::ELEMENT;
        alternate_style->block_style_type = EBlockStyleType::Unspecified;
    }
    //Add refinement styles
    if (const auto s = EnhancementStyle()) {
        style += *s;
        if (alternate_style)
            *alternate_style += *s;
    }
    //add the running style. Do not add it to the main block,
    //since it carries the result of any 'use' command after the parse
    //(In case of all other blocks have already popped the context stack,
    //so any changes to the running style in their content is gone. Not so
    //for the main block, whose context is not enclosed in {}s.)
    if (name_unique!="(MAIN)") {
        style += chart.MyCurrentContext().running_style_blocks.read();
        if (alternate_style)
            *alternate_style += chart.MyCurrentContext().running_style_blocks.read();
    }
    //if we are around, add "around" - note we may later remove all blocks
    //from around if they are faulty, but hey the style is already added here.
    if (around.size()) {
        style += chart.MyCurrentContext().styles["around"].read();
        if (alternate_style)
            *alternate_style += chart.MyCurrentContext().styles["around"].read();
    }
    //cater for the default text
    style.ApplyTextBefore(chart.MyCurrentContext().text);
    if (alternate_style)
        alternate_style->ApplyTextBefore(chart.MyCurrentContext().text);
    //Add the attributes, we have collected
    style += attributes;
    if (alternate_style) *alternate_style += attributes;
    if (style.label) {
        //Resolve parameter and style values
        StringFormat basic = style.text;
        StringFormat::ExpandReferences(*style.label, &chart, style.label.file_pos, &basic,
                                       false, true, StringFormat::LABEL, true, "\\*"); //Keep \* escapes as they are - to be resolved later (for
    }
}

void BlockBlock::MakeTemplate()
{
    RemoveFromDrawOrder();
    UnRegisterNames();
    for (auto &pInstr:content)
        pInstr->MakeTemplate();
}

/** After we have copied the attributes/style of the original block to an
 * element of a multi block series, this adjusts alignment, size and label attrs.*/
void BlockBlock::AdjustMultiAttributes(unsigned seq, const XY &offset)
{
    style.alignment.Empty();
	//external margin is on the whole multiblock series
	for (unsigned xy = 0; xy<2; xy++) {
		style.min_margin[xy] = 0;
		style.max_margin[xy] = 0;
	}
	if (seq==0) return;
    style.alignment.Top = {true, false, EStyleType::ELEMENT,
                                { {{"last", {}}}, {0.0, false, offset.y*seq}, EAlignPrio::Explicit},
                                "multi"};
    style.alignment.Bottom = {true, false, EStyleType::ELEMENT,
                                    { {{"last", {}}}, {1.0, false, offset.y*seq}, EAlignPrio::Explicit},
                                    "multi"};
    style.alignment.Left = {true, false, EStyleType::ELEMENT,
                                { {{"last", {}}}, {0.0, false, offset.x*seq}, EAlignPrio::Explicit},
                                "multi"};
    style.alignment.Right = {true, false, EStyleType::ELEMENT,
                                { {{"last", {}}}, {1.0, false, offset.x*seq}, EAlignPrio::Explicit},
                                "multi"};
    //clear label - only the first one has it
    style.label.reset();
    //clear any size attribute only first one has it
    style.size[0].reset();
    style.size[1].reset();
	//Kill shadow on all, but the first element
	style.shadow.offset = 0;
}

/** Common helper function to apply translate alignment modifiers to an AlignmentAttr (of explicit priority).
 * @param [in] chart The chart to emit errors to.
 * @param [in] c1 The modifier to apply.
 * @param [in] c2 The second modifier to apply. Shall equal to 'c1' if only one modifier to apply.
 *                If opposite or conflicting to 'c1', we emit an error and ignore it.
 * @param [in] l1 The location of 'c1' in the input file (for error msgs).
 * @param [in] l2 The location of 'c2' in the input file (for error msgs).
 * @param [in] xblocks The blocks to align to in the x direction.
 * @param [in] yblocks The blocks to align to in the y direction.
 * @param [in] xmin The edge specification of the block(s) in xblocks to be used for 'left' and 'leftof'.
 * @param [in] xmax The edge specification of the block(s) in xblocks to be used for 'right' and 'rightof'.
 * @param [in] ymin The edge specification of the block(s) in yblocks to be used for 'top' and 'above'.
 * @param [in] ymax The edge specification of the block(s) in yblocks to be used for 'bottom' and 'below'.
 * @param [in] keep_margin_received If true, we use the 'margin' field in xmin,xmax,ymin,ymax.
 *                                  (This is applicable when aligning to a coordinate. Whatever specified by the
 *                                  user there, we just take.) If false, we set 'margin' to true for
 *                                  the direction of major modifiers (X for leftof and rightof, Y for above and below),
 *                                  and false for the centering in other direction or for minor modifiers.
 * @param [in] no_offset_for_minor If set and we have a major and a minor alignment modifier,
 *                                 We will use offset of zero for the direction of the minor modifier
 *                                 instead of the one in xmin,xmax,ymin,ymax.
 * @param [in] no_offset_for_center If set offset of center alignment (for single major modifiers)
 *                                  will use zero offset as opposed to the one received in {y,x}{min,max}
 * @returns the translated AttributeAttr or null on error.*/
AlignmentAttr *
BlockBlock::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)
{
    if (c1.major && c2.major && c1.dir==Opposite(c2.dir)) {
        chart.Error.Error(l2, "Cannot apply two opposite place modifiers. Ignoring both.");
        return nullptr;
    }
    bool is_solo = c1.major==c2.major && c1.dir==c2.dir;
    if (!is_solo && IsHorizontal(c1.dir)==IsHorizontal(c2.dir)) {
        chart.Error.Error(l2, "This alignment modifier conflicts the previous one. Ignoring it.");
        chart.Error.Error(l1, l2, "This is the conflicted alignment modifier, I keep using.");
        is_solo = true;
        c2 = c1;
    }
    std::string solo = is_solo ? "solo " : "";
    _ASSERT(xblocks && yblocks);
    _ASSERT(xblocks->size() && yblocks->size());
    _ASSERT(xmin.margin==xmax.margin);
    _ASSERT(xmin.offset==xmax.offset);
    _ASSERT(ymin.margin==ymax.margin);
    _ASSERT(ymin.offset==ymax.offset);

    const EdgePos xmid = {(xmin.pos+xmax.pos)/2, xmin.margin, no_offset_for_center ? 0 : xmin.offset};
    const EdgePos ymid = {(ymin.pos+ymax.pos)/2, ymin.margin, no_offset_for_center ? 0 : ymin.offset};

    //When no_offset_for_minor is set:
    //if there is a major alignment modifier (above/below/leftof/rightof) then the minor ones
    //(top/bottom/left/right) will have has zero offset. To do this we zero out offset for
    //minor modifiers below.
    //If there is just a minor modifier, we take the offset as received.

    //update the flag
    if (is_solo) no_offset_for_minor = false;

    AlignmentAttr *ret = new AlignmentAttr;
    switch (c1.dir) {
    case EDirection::Above:
        if (c1.major) {
            //this is "above"
            ret->Bottom = {true, true, EStyleType::ELEMENT,{*yblocks, ymin, EAlignPrio::Explicit}, solo + "'above' modifier"};
            if (is_solo) ret->XCenter = {true, false, EStyleType::ELEMENT,{*xblocks, xmid, EAlignPrio::Explicit}, solo + "'above' modifier"};
			if (!keep_margin_received) {
				ret->Bottom.value.edge.margin = true;
				ret->XCenter.value.edge.margin = false;
			}
		} else {
            //This is "top"
            ret->Top = {true, false, EStyleType::ELEMENT,{*yblocks, ymin, EAlignPrio::Explicit}, solo + "'top' modifier"};
			if (!keep_margin_received) ret->Top.value.edge.margin = false;
			if (no_offset_for_minor) ret->Top.value.edge.offset = 0;
        }
        break;
    case EDirection::Below:
        if (c1.major) {
            //this is "below"
            ret->Top = {true, true, EStyleType::ELEMENT,{*yblocks, ymax, EAlignPrio::Explicit}, solo + "'below' modifier"};
			if (is_solo) ret->XCenter = {true, false, EStyleType::ELEMENT,{*xblocks, xmid, EAlignPrio::Explicit}, solo + "'below' modifier"};
			if (!keep_margin_received) {
				ret->Top.value.edge.margin = true;
				ret->XCenter.value.edge.margin = false;
			}
		} else {
            //this is "bottom"
            ret->Bottom = {true, false, EStyleType::ELEMENT,{*yblocks, ymax, EAlignPrio::Explicit}, solo + "'bottom' modifier"};
			if (!keep_margin_received) ret->Bottom.value.edge.margin = false;
			if (no_offset_for_minor) ret->Bottom.value.edge.offset = 0;
        }
        break;
    case EDirection::Left:
        if (c1.major) {
            //this is "leftof"
            ret->Right = {true, true, EStyleType::ELEMENT,{*xblocks, xmin, EAlignPrio::Explicit}, solo + "'leftof' modifier"};
			if (is_solo) ret->YMiddle = {true, false, EStyleType::ELEMENT, {*yblocks, ymid, EAlignPrio::Explicit}, solo + "'leftof' modifier"};
			if (!keep_margin_received) {
				ret->Right.value.edge.margin = true;
				ret->YMiddle.value.edge.margin = false;
			}
		} else {
            //this is  "left"
            ret->Left = {true, false, EStyleType::ELEMENT, {*xblocks, xmin, EAlignPrio::Explicit}, solo + "'left' modifier"};
			if (!keep_margin_received) ret->Left.value.edge.margin = false;
			if (no_offset_for_minor) ret->Left.value.edge.offset = 0;
        }
        break;
    case EDirection::Right:
        if (c1.major) {
            //this is "rightof"
            ret->Left = {true, true, EStyleType::ELEMENT, {*xblocks, xmax, EAlignPrio::Explicit}, solo + "'rightof' modifier"};
			if (is_solo) ret->YMiddle = {true, false, EStyleType::ELEMENT, {*yblocks, ymid, EAlignPrio::Explicit}, solo + "'rightof' modifier"};
			if (!keep_margin_received) {
				ret->Left.value.edge.margin = true;
				ret->YMiddle.value.edge.margin = false;
			}
		} else {
            //this is "right"
            ret->Right = {true, false, EStyleType::ELEMENT,{*xblocks, xmax, EAlignPrio::Explicit}, solo + "'rightof' modifier"};
			if (!keep_margin_received) ret->Right.value.edge.margin = false;
			if (no_offset_for_minor) ret->Right.value.edge.offset = 0;
        }
        break;
    default:
        _ASSERT(0);
        break;
    }
    //If the second keyword is of a minor one, we kill the offset
    if (!c2.major && no_offset_for_minor)
        xmin.offset = xmax.offset = ymin.offset = ymax.offset = 0;
    if (!keep_margin_received)
        xmin.margin = xmax.margin = ymin.margin = ymax.margin = c2.major;
    if (!is_solo) switch (c2.dir) {
    case EDirection::Above:
        if (c2.major)
            ret->Bottom = {true, true, EStyleType::ELEMENT, {*yblocks, ymin, EAlignPrio::Explicit}, solo + "'above' modifier"};
        else
            ret->Top = {true, false, EStyleType::ELEMENT, {*yblocks, ymin, EAlignPrio::Explicit}, solo + "'top' modifier"};
        break;
    case EDirection::Below:
        if (c2.major)
            ret->Top = {true, true, EStyleType::ELEMENT, {*yblocks, ymax, EAlignPrio::Explicit}, solo + "'below' modifier"};
        else
            ret->Bottom = {true, false, EStyleType::ELEMENT, {*yblocks, ymax, EAlignPrio::Explicit}, solo + "'bottom' modifier"};
        break;
    case EDirection::Left:
        if (c2.major)
            ret->Right = {true, true, EStyleType::ELEMENT, {*xblocks, xmin, EAlignPrio::Explicit}, solo + "'leftof' modifier"};
        else
            ret->Left = {true, false, EStyleType::ELEMENT, {*xblocks, xmin, EAlignPrio::Explicit}, solo + "'left' modifier"};
        break;
    case EDirection::Right:
        if (c2.major)
            ret->Left = {true, true, EStyleType::ELEMENT, {*xblocks, xmax, EAlignPrio::Explicit}, solo + "'rightof' modifier"};
        else
            ret->Right = {true, false, EStyleType::ELEMENT, {*xblocks, xmax, EAlignPrio::Explicit}, solo + "'right' modifier"};
        break;
    default:
        _ASSERT(0);
        break;
    }
    return ret;
}

/** Translate alignment modifiers having a block series to an AlignmentAttr (of explicit priority).
 * This will be called * for constructs like "leftof A+B" or "above right C" or "below"
 * (latter meaning "below prev").
 * @param [in] chart The chart to emit errors to.
 * @param [in] c1 The modifier to apply.
 * @param [in] c2 The second modifier to apply. Shall equal to 'c1' if only one modifier to apply.
 *                If opposite or conflicting to 'c1', we emit an error and ignore it.
 * @param [in] l1 The location of 'c1' in the input file (for error msgs).
 * @param [in] l2 The location of 'c2' in the input file (for error msgs).
 * @param blocks The list of blocks. May be empty, then 'prev' is assumed.
 * @returns the translated AttributeAttr or null on error.*/
AlignmentAttr *
BlockBlock::TranslatePre(BlockChart &chart, AlignModifier c1, AlignModifier c2,
                         const FileLineCol &l1, const FileLineCol &l2,
                         gsl::owner<StringWithPosList *> blocks)
{
    double off=0;
    if (blocks==nullptr)
        blocks = new StringWithPosList;
    if (blocks->size() && !from_chars(blocks->back().name, off))
		blocks->pop_back(); //We had a number at the end. It is now in 'off', pop it.
    if (blocks->size()==0)    //parsed no good *blocks: an error was already reported or we are a legal single number. Assume prev
        blocks->emplace_back("prev", FileLineColRange());
    auto ret =
        BlockBlock::TranslatePre(chart, c1, c2, l1, l2, blocks, blocks,
                                 EdgePos(0.0, true, off), EdgePos(1.0, true, off),
                                 EdgePos(0.0, true, off), EdgePos(1.0, true, off),
                                 false, true, true);
    delete blocks;
    return ret;
}

/**  Translate alignment modifiers having for coordinates to an AlignmentAttr (of explicit priority).
 * This will be called for constructs like "leftof (A@top, B@bottom+10)".
 * @param [in] chart The chart to emit errors to.
 * @param [in] c1 The modifier to apply.
 * @param [in] c2 The second modifier to apply. Shall equal to 'c1' if only one modifier to apply.
 *                If opposite or conflicting to 'c1', we emit an error and ignore it.
 * @param [in] l1 The location of 'c1' in the input file (for error msgs).
 * @param [in] l2 The location of 'c2' in the input file (for error msgs).
 * @param [in] coord The coordinates text. If invalid or only one of the coordinates is set,
 *                   we emit an error.
 * @param [in] lc The location of the 'coord' in the input file.
 * @returns the translated AttributeAttr or null on error.*/
AlignmentAttr *
BlockBlock::TranslatePre(BlockChart &chart, AlignModifier c1, AlignModifier c2,
                         const FileLineCol &l1, const FileLineCol &l2,
                         std::string_view coord, const FileLineCol &lc)
{
    auto c = ParseCoord(coord, &chart.Error, &lc, "alignment modifier");
    if (!c) return nullptr;
    if (c->at(0).blocks.size()==0 || c->at(1).blocks.size()==0) {
        chart.Error.Error(lc, "I need both coordinates for alignment modifiers. Ignoring it.");
        return nullptr;
    } else {
        return
            BlockBlock::TranslatePre(chart, c1, c2, l1, l2, &c->at(0).blocks, &c->at(1).blocks,
                                     c->at(0).edge, c->at(0).edge,
                                     c->at(1).edge, c->at(1).edge,
                                     true, false, false);
    }
}

void BlockBlock::ApplyPre(gsl::owner<AlignmentAttr *> local_alignment)
{
    if (local_alignment) {
        //We key the priorities as in 'local_alignment'. I expect them all to be 'Explicit' as
        //TranslatePre() always returns that.
        local_alignment->MergeByDimension(style.alignment);
        style.alignment = *local_alignment;
        delete local_alignment;
    }
}

/** When a user changes attributes after definition this will be called.*/
void BlockBlock::ApplyFurtherAttributes(const AttributeList * al)
{
	for (auto &attr : *al)
		if (!AddAttribute(*attr))
			chart.Error.Error(*attr, false, "Unrecognized or inapplicable attribute. Ignoring it.");
}

void BlockBlock::AddAround(gsl::owner<StringWithPosList*> blks, const FileLineCol&)
{
    _ASSERT(blks && blks->size());
    if (blks==nullptr) return;
    if (!valid) return;
    around = std::move(*blks);
    remove_if(around, [](const auto&a) {return a.name.length()==0; });
    delete blks;
    if (collapsed.value_or(false)) {
        chart.Error.Error(orig_collapsed.file_pos, "Blocks defined with 'around' cannot be collapsed. Ignoring attribute.");
        orig_collapsed = false;
        collapsed = false;
    }
}


void BlockBlock::ApplyCloneModifiers(gsl::owner<CloneActionList *> mod)
{
    if (!mod) return;
    for (auto &ca : *mod) {
        //look up 'block' and 'before' within 'content' if it is specified.
        struct {
            std::string_view item;
            bool operator () (std::unique_ptr<BlockInstruction> &pContent) {
                BlockBlock * b = dynamic_cast<BlockBlock*>(pContent.get());
                return b && b->name_original==item;
            }
        } lookup;
        lookup.item = ca->before;
        const BlockInstrList::iterator before = ca->before.size()==0 ? content.end() :
            std::find_if(content.begin(), content.end(), lookup);
        if (ca->before.length() && before==content.end()) {
            chart.Error.Error(ca->before_pos.start, StrCat("Block ", ca->before, " is not among the content of the cloned block. Ignoring 'before' clause and append/move to the end."));
        }
        //increment the content and make us their parent
        if (ca->instr)
            for (auto &pInstr : *ca->instr) {
                BlockBlock *bb = dynamic_cast<BlockBlock*>(pInstr.get());
                if (bb)
                    bb->parent = this;
                pInstr->IncLevel();
            }
        if (ca->action==ECloneContentAdjustment::ADD) {
            _ASSERT(ca->blocks.size()==0);
            if (ca->instr) {
                //Insert the elements to add just before 'before' in draw order
                for (auto &pInst : *ca->instr)
                    pInst->AddToDrawOrder(before==content.end() ? nullptr : before->get());
                content.splice(before, *ca->instr);
            }
            continue;
        }
        //Now sanitize the ca->blocks list.
        //For update, we accept 'all', 'blocks' or 'arrows', as well, as block names.
        //For others (except ADD handled above, we only accept block names)
        EUseKeywords use = EUseKeywords::USE_KEYWORD_NONE;
        StringWithPosList bad_blocks;
        std::vector<std::pair<BlockInstrList*, BlockInstrList::iterator>> blocks;
        for (auto &sp : ca->blocks) {
            lookup.item = sp.name;
            //Determine if this is the case for the first thing to update. If not, 'local_use' will be NONE.
            EUseKeywords local_use = ca->action!=ECloneContentAdjustment::UPDATE ? EUseKeywords::USE_KEYWORD_NONE :
                CaseInsensitiveEqual(lookup.item, "all") ? EUseKeywords::USE_KEYWORD_ALL :
                CaseInsensitiveEqual(lookup.item, "blocks") ? EUseKeywords::USE_KEYWORD_BLOCKS :
                CaseInsensitiveEqual(lookup.item, "arrows") ? EUseKeywords::USE_KEYWORD_ARROWS :
                EUseKeywords::USE_KEYWORD_NONE;
            //Now lookup the first element in the list of elements.
            //Note that for delete, move and replace there must be 1 element, for update there must be 1+
            //for add it must be zero.
            BlockInstrList::iterator block = lookup.item.size()==0 || local_use!=EUseKeywords::USE_KEYWORD_NONE ? content.end() :
                std::find_if(content.begin(), content.end(), lookup);
            if (lookup.item.length() && local_use==EUseKeywords::USE_KEYWORD_NONE && block==content.end()) {
                //We did not find the named block.
                //For 'Update', 'Replace' and 'drop' actions, we also accept dot specific names.
                //Check if sp.name is a dotted name and try searching for it in 'content'
                std::pair<BlockInstrList*, BlockInstrList::iterator> hit{&content, content.end()}; //invalid if first->end()==second
                if ((ca->action==ECloneContentAdjustment::DROP || ca->action==ECloneContentAdjustment::REPLACE ||
                    ca->action==ECloneContentAdjustment::UPDATE) && sp.name.find('.')!=std::string::npos) {
                    std::string remaining = sp.name;
                    while (1) {
                        lookup.item = std::string_view(remaining).substr(0, remaining.find('.'));
                        hit.second = std::find_if(hit.first->begin(), hit.first->end(), lookup);
                        remaining.erase(0, lookup.item.length()+1);
                        if (hit.second==hit.first->end()) break; //not found
                        if (remaining.length()==0) break; //found ultimate block, we are done
                        //still some nesting to go. See if the element we found is a block with
                        //elements inside
                        BlockBlock *bb = dynamic_cast<BlockBlock*>(hit.second->get());
                        if (bb==nullptr) {
                            //No more nesting - we did not find it.
                            hit.second = hit.first->end();
                            break;
                        }
                        hit.first = &bb->content;
                    }
                }
                if (hit.first->end()==hit.second) {
                    //not found - add to error list
                    bad_blocks.push_back(std::move(sp));
                    sp.name.clear();
                } else {
                    //found a nested one, good
                    blocks.push_back(hit);
                }
                continue;
            }
            if (local_use == EUseKeywords::USE_KEYWORD_NONE)
                blocks.emplace_back(&content, block);
            else
                use = use | local_use;
        }
        //Remove empty names (some of which was bad and moved to bad_blocks)
        ca->blocks.erase(std::remove_if(ca->blocks.begin(), ca->blocks.end(), [](const StringWithPos &sp) {return sp.name.length()==0; }), ca->blocks.end());
        if (ca->blocks.size()==0 && use == EUseKeywords::USE_KEYWORD_NONE) {
            if (bad_blocks.size()==0)
                chart.Error.Error(ca->action_pos.start, "Internal error: a block name need to be specified for this action. Ignoring action.");
            else if (bad_blocks.size()==1)
                chart.Error.Error(bad_blocks.front().file_pos.start, StrCat("Block ", bad_blocks.front().name, " is not among the content of the cloned block. Ignoring this content update action."));
            else
                chart.Error.Error(bad_blocks.front().file_pos.start, "None of the listed blocks are among the content of the cloned block. Ignoring this content update action.");
            continue;
        }
        for (auto &sp:bad_blocks)
            chart.Error.Error(sp.file_pos.start, StrCat("Block ", sp.name, " is not among the content of the cloned block. Ignoring it."));
        switch (ca->action) {
        default:
        case ECloneContentAdjustment::ADD: //Already handled above
            _ASSERT(0);
            break;
        case ECloneContentAdjustment::MOVE:
            for (auto &b : blocks) {
                (*b.second)->AddToDrawOrder(before==content.end() ? nullptr : before->get());
                content.splice(before, content, b.second);
            }
            break;
        case ECloneContentAdjustment::DROP:
            for (auto &b : blocks)
                b.first->erase(b.second);
            break;
        case ECloneContentAdjustment::UPDATE:
            if ((use & EUseKeywords::USE_KEYWORD_BLOCKS) && blocks.size()) {
                chart.Error.Warning(ca->blocks.front().file_pos.start, "You have specified either 'all' or 'blocks', which will update all blocks. Specifying additional block names is redundant.");
                blocks.clear();
            }
            if (ca->recursive && blocks.size())
                chart.Error.Error(ca->action_pos.start, "When updating individual blocks, we cannot be recursive. Ignoring keyword.");
            if (blocks.size()==1 && use == EUseKeywords::USE_KEYWORD_NONE) {
                //Update one block. We may have content modifiers, too
                (*blocks.front().second)->AddAttributeList(ca->attr.release());
                dynamic_cast<BlockBlock*>(blocks.front().second->get())->ApplyCloneModifiers(ca->modifiers.release());
                continue;
            }
            //OK here we definitely update more than one block/arrow
            if (ca->modifiers && ca->modifiers->size())
                chart.Error.Error(ca->modifiers->front()->action_pos.start,
                                  "When updating multiple elements, we cannot change their content. Ignoring these.");
            if (ca->attr && ca->attr->size()) {
                StyleCoW<BlockStyle> save_running_style_blocks = chart.MyCurrentContext().running_style_blocks;
                StyleCoW<BlockStyle>save_running_style_arrows = chart.MyCurrentContext().running_style_arrows;
                chart.MyCurrentContext().running_style_blocks.write().Empty();
                chart.MyCurrentContext().running_style_arrows.write().Empty();
                if (blocks.size())
                    chart.AddAttributeListToRunningStyle(ca->attr.release(), use | USE_KEYWORD_BLOCKS);
                else
                    chart.AddAttributeListToRunningStyle(ca->attr.release(), use);
                //Update the individually listed blocks
                for (auto &b: blocks)
                    (*b.second)->ApplyRunningStyle(true, false);
                //Update all blocks or arrows, may be recursive
                if (use != EUseKeywords::USE_KEYWORD_NONE)
                    for (auto &pInstr : content)
                        pInstr->ApplyRunningStyle(true, ca->recursive);
                chart.MyCurrentContext().running_style_blocks = std::move(save_running_style_blocks);
                chart.MyCurrentContext().running_style_arrows = std::move(save_running_style_arrows);
            }
            break;
        case ECloneContentAdjustment::REPLACE:
            if (blocks.size()==0) {
                chart.Error.Error(ca->action_pos.start, "Internal error: missing block name.");
                continue;
            }
            if (blocks.size()>1)
                chart.Error.Error(ca->blocks[1].file_pos.start, "Only one block can be replaced at a time. Ignoring the block names after the first.");
            blocks.front().first->erase(blocks.front().second++); //delete the instruction to replace
            //Insert the elements to add just before 'before' in draw order
            for (auto &pInst : *ca->instr)
                pInst->AddToDrawOrder(blocks.front().second==blocks.front().first->end() ? nullptr : blocks.front().second->get());
            blocks.front().first->splice(blocks.front().second, *ca->instr); //splice in the list of entities to replace to
            break;
        }
    }
    delete mod;
    //reset ranks according to the potentially new order of content
    unsigned rank = 0;
    for (auto &pInstr : content) {
        auto b = dynamic_cast<BlockBlock*>(pInstr.get());
        if (b) b->rank = rank++;
    }
}

/** Checks if the GUI has a collapse/expand command for us and
 * updates 'collapsed' if so.*/
void BlockBlock::UpdateCollapsedFromGUI() {
    if (CanBeCollapsed().empty())
        if (auto i = chart.GUI_State.collapse.find(name_gui_collapse); i!=chart.GUI_State.collapse.end()) {
            if (collapsed.value_or(false)==i->second.collapsed)
                chart.GUI_State.collapse.erase(i);
            else {
                collapsed = i->second.collapsed;
                i->second.element_exists = true;
            }
        }
}

BlockBlock *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
{
    BlockBlock *ret = new BlockBlock(*this, file_pos.CreatePushed(l, EInclusionReason::COPY), name, prefix);
    ret->RegisterNames();
    ret->style.Empty();
    ret->AddAttributeList(attr);
    ret->ApplyCloneModifiers(mod);
    BlockStyle attributes = std::move(ret->style);
    ret->style = style;
    ret->style += attributes;
    if (alternate_style) {
        ret->alternate_style = alternate_style;
        *ret->alternate_style += attributes;
    }
    //We key the priorities set in 'align' as they are. I expect they are to be 'Explicit' as
    //TranslatePre() generates such alignments.
    if (align) {
        ret->style.alignment.MergeByDimension(*align);
        if (ret->alternate_style)
            ret->alternate_style->alignment.MergeByDimension(*align);
        delete align;
    }
    //Push all attributes
    const FileLineCol inclusion(l.start.TopOfStack());
    ret->style.Push(inclusion);
    if (ret->alternate_style)
        ret->alternate_style->Push(inclusion);
    //Now handle collapse. At this point
    //'ret->content' contains the user specified content irrespective of whether 'this' is collapsed or not
    //'ret->style' and 'ret->alternative_style', on the other hand are exactly as for 'this', that is, if
    //'this' is collapsed with no indicator, 'ret->style' will be based on style 'block' and 'ret->alternative_style'
    //will be based on style 'container'; else the opposite.
    if (multi_parent)
        ret->name_gui_collapse = multi_parent->name_gui_collapse;
    ret->UpdateCollapsedFromGUI();
    if (ret->collapsed.value_or(false) && ret->orig_has_visible_content) {
        ret->collapsed_content.swap(ret->content);
        _ASSERT(ret->content.empty());
        if (ret->style.indicator.value_or(true)) {
            auto p = std::make_unique<BlockIndicator>(chart, ret->file_pos);
            p->rank = 0;
            p->SetParent(ret);
            p->SetContent(nullptr); //This requires a parent
            p->IncLevel();
            ret->content.Append(std::move(p));
        }
        ret->has_visible_content = ret->style.indicator.value_or(true);
    } else
        ret->has_visible_content = ret->orig_has_visible_content;
    if (ret->has_visible_content != has_visible_content)
        std::swap(*ret->alternate_style, ret->style);
    return ret;
}

void BlockBlock::ApplyRunningStyle(bool cloning, bool recursive)
{
    style += chart.MyCurrentContext().running_style_blocks.read();
    if (recursive)
        for (auto &p:content)
            p->ApplyRunningStyle(cloning, recursive);
}


/** Find out which bloc is a common ancestor of us and other.*/
BlockBlock *BlockBlock::GetCommonParent(const BlockBlock *other)
{
    for (auto my = this; my; my = my->parent)
        for (auto her = other; her; her = her->parent)
            if (my==her) return my;
    _ASSERT(0);
    return nullptr;
}

/** Find out which bloc is a common ancestor of us and other.*/
const BlockBlock *BlockBlock::GetCommonParent(const BlockBlock *other) const
{
    for (auto my = this; my; my = my->parent)
        for (auto her = other; her; her = her->parent)
            if (my==her) return her;
    _ASSERT(0);
    return nullptr;
}

std::unordered_set<const BlockBlock*>
BlockBlock::GetMyParents(const BlockBlock *p) const
{
    std::unordered_set<const BlockBlock*> ret;
    for (const BlockBlock *q = this; q && q!=p; q = q->parent)
        ret.insert (q);
    return ret;
}

double BlockBlock::GetDistanceForMe(double distance, const StringWithPosNumberList & distance_per_block) const
{
    auto i = std::find_if(distance_per_block.begin(), distance_per_block.end(),
                          [this](auto &spn) {return spn.name==this->name_unique; });
    return i==distance_per_block.end() ? distance : i->number;
}

/** Return the expanded blocks of us except for the ones covering any point in 'except',
 * and blocks in 'except_blocks' and any block who has a parent in 'except_parents'.
 * We expand the returned block by 'distance', except if our name is in 'distance_per_block',
 * in which case we use that distance.
 * We return our expanded block, if the arrows should go around us.
 * We return an empty Contour if the arrows can cross us and our children (if we have any).
 * We return no value, if the arrow can go through us, but not through our children.
 * lw2 is the half linewidth of the arrow to lay out*/
std::optional<Contour>
BlockBlock::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
{
    std::optional<Contour> ret = Contour();
    //if we are in except_parents, the arrow may cross all our children
    for (auto &sp : except_parents)
        if (IsMyParent(chart.GetBlockByName(sp.name, parent, true, true)[0]))
            return ret;
    bool allow = skip_allow_arrows && style.allow_arrows.value_or(false);
    XY dummy;
    const double my_distance = GetDistanceForMe(distance, distance_per_block);
    for (auto &xy : except)
        if (allow |= area.Distance(xy, dummy)<my_distance+lw2) break;
    for (auto &sp : except_blocks)
        if (allow |= (name_unique==sp.name)) break;
    //If 'allow' is true, the arrow may cross us, but not any of our children
    if (allow)
        ret.reset();
    else
        ret = area.CreateExpand(my_distance+lw2);
    return ret;
}


/** Return the expanded blocks of us and our children except for the ones covering any point in 'except',
 * and blocks in 'except_blocks' and any block who has a parent in 'except_parents'.
 * We expand each block added by 'distance', except if its full name is in 'distance_per_block',
 * in which case we use that distance.
 * lw2 is the half linewidth of the arrow to lay out*/
Contour BlockBlock::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
{
    auto ret = GetContourForArrowsHelper(except, skip_allow_arrows, except_blocks, except_parents,
                                         distance, distance_per_block, lw2);
    if (!ret.has_value()) {
        ret.emplace();
        for (auto &b : content)
            ret.value() += b->GetContourForArrows(except, skip_allow_arrows, except_blocks, except_parents,
                                                 distance, distance_per_block, lw2);
    }
    return ret.value();
}

Contour BlockBlock::GetVisibleCover() const
{
    Contour ret;
    if (Visible(type)) ret = GetAreaToDraw();
    else for (auto &b : content) {
        auto bb = dynamic_cast<const BlockBlock*>(b.get());
        if (bb) ret += bb->GetVisibleCover();
    }
    return ret;
}



/** Return true if any block on the list is either this or a descendant. */
bool BlockBlock::IsMyChild(const std::unordered_set<const BlockBlock*> &except) const
{
    if (except.find(this)!=except.end()) return true;
    for (auto &b : content)
        if (b->IsMyChild(except)) return true;
    return false;
}


/** If a child of us has one of its X or Y alignment set,
 * kill our x or y alignment attributes (resp) of lower prio.*/
void BlockBlock::KillAlignmentBasedOnChildAttr(bool y, EAlignPrio p)
{
    if (style.alignment.GetMin(y).is_set && style.alignment.GetMin(y).value.prio<p)
        style.alignment.GetMin(y).is_set = false;
    if (style.alignment.GetMid(y).is_set && style.alignment.GetMid(y).value.prio<p)
        style.alignment.GetMid(y).is_set = false;
    if (style.alignment.GetMax(y).is_set && style.alignment.GetMax(y).value.prio<p)
        style.alignment.GetMax(y).is_set = false;
}

bool BlockBlock::PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number)
{
    if (!valid) {
        chart.GUI_State.collapse.erase(name_unique);
        chart.GUI_State.inlined.erase(name_unique);
        return false;
    }
    //Remove any accidental label from cols, rows, spaces and cells
	if (!Visible(type)) style.label.reset();
	//Check if the user has set the 'size_mode' directly on this element
	if (style.label && style.label_mode && has_visible_content && style.label_mode != ELabelMode::ENLARGE) {
		chart.Error.Error(style.label_mode.file_pos, "The attribute 'label_mode' can only be 'enlarge', for blocks with content. Ignoring it.");
		style.label_mode = ELabelMode::ENLARGE;
	}
    hidden_by = hide_by;
    if (hidden_by) RemoveFromDrawOrder();
    if (around.size()) {
        //Sanitize blocks in 'around' clause
        Resolve(around, {nullptr, nullptr, nullptr, nullptr, parent}, {},
                {},
                "Use of '%b' in the 'around' clause is not allowed. Ignoring block.", nullptr, nullptr);
        CheckExists(around, "block", {HandleHidden::Keep},
                    "This block cannot be around itself. Ignoring it for the 'around' clause.");
        if (around.empty()) {
            chart.Error.Error(file_pos.start, "No valid blocks listed in 'around' clause. Ignoring entire block.");
            return false;
        }
        auto original_around = around;
        if (CheckExists(around, "block", { HandleHidden::Remove })) //all blocks disappeared due to collapse. This is not an error, just skip this 'around' block, too.
            return false;
        if (around.size()!=original_around.size()) { //some blocks disappeared, some not. Report to user.
            around = std::move(original_around);
            CheckExists(around, "block", { HandleHidden::Remove, "This block is hidden because block '%B' is collapsed. Ignoring it for the 'around' clause." });
        }
        style.alignment.Empty(); //if we have around, do not align anywhere
        has_visible_content = true;
        //if our label pos was 'center' (coming from the style 'block' potentially), we need to adjust
        if (style.label_pos.value_or(EDirection::Above)==EDirection::Invalid)
            style.label_pos = EDirection::Above;
    }
    bool ret = BlockInstruction::PostParseProcess(canvas, hide_by, number);
    //Change our displayname
    //0. for the main block we set it to "(MAIN)"
    //1. if our original (unprefixed) name is unique, we use that
    //2. else if our full name is unique, we use that
    //3. else we disambiguate the full name
    if (parent==nullptr) {
        _ASSERT(name_unique=="(MAIN)");
        name_display = "(MAIN)";
    } else if (name_original.length()==0) {
        if (file_pos.start.IsValid())
            name_display = StrCat("the block at ",
                                  std::to_string(file_pos.start.line),
                                  ':',
                                  std::to_string(file_pos.start.col));
    } else {
        const auto range_orig = chart.BlockNames_Original.equal_range(name_original);
        _ASSERT(range_orig.first!=chart.BlockNames_Original.end());
        if (std::next(range_orig.first)==range_orig.second) {
            //unique original name
            name_display = name_original;
        } else {
            const auto range_full = chart.BlockNames_Full.equal_range(name_full);
            if (std::next(range_full.first)==range_full.second) {
                //unique full name
                name_display = name_full;
            } else {
                //We have more than one with this original name
                if (file_pos.start.IsValid())
                    name_display = StrCat(name_original, " at ",
                                          std::to_string(file_pos.start.line),
                                          ':',
                                          std::to_string(file_pos.start.col));
            }
        }
    }
    //insert us among the references
    if (name_full.length()) {
        chart.ReferenceNames[name_full].number_text = number_text;
        chart.ReferenceNames[name_full].linenum = file_pos.start;
    }
    //if our shape has no label placeholder, but we have either label or content, emit error.
    if (shape>=0 && !chart.Shapes.HasLabelPos(shape)) {
		if (has_visible_content) {
			chart.Error.Error(file_pos.start, "This block has content, but its shape ('" +
							  chart.Shapes[shape].name + "') has no label placeholder. Ignoring block.");
			ret = false;
		} else if (style.label && style.label->length()) {
			//Only emit an error if the label is not the default "\*"
			if (style.label!="\\*")
				chart.Error.Error(file_pos.start, "This block has label, but its shape ('" +
								  chart.Shapes[shape].name + "') has no label placeholder. Ignoring label.");
			style.label.reset();
		}
    }
    //If we are collapsed (but none of our parents are), indicate this to all children.
    if (!hide_by && collapsed.value_or(false))
        hide_by = this;
    if (style.numbering && style.label && *style.numbering)
        number.SetDecrementOnSizeIncrease(numberingStyle.Last().increment);
    //We call PostParseProcess on collapsed content
    for (auto &pChild : collapsed_content)
        if (!pChild->PostParseProcess(canvas, hide_by, number))
            pChild.reset(nullptr); //also deletes
    collapsed_content.remove(nullptr);
    //We also call PostParseProcess on content - which may be
    //- regular content if we are not collapsed
    //- an indicator if we are collapsed.
    //In any case we shall not hide it if we are in hidden by
    for (auto& pChild : content)
        if (!pChild->PostParseProcess(canvas, hide_by==this ? nullptr : hide_by, number))
            pChild.reset(nullptr); //also deletes
    content.remove(nullptr);
    if (style.numbering && style.label && *style.numbering)
        number.CancelDecrementOnSizeIncrease();
    if (orig_has_visible_content && CanBeCollapsed().empty())
        controls.push_back(collapsed.value_or(false) ? EGUIControlType::EXPAND : EGUIControlType::COLLAPSE);

    //At most 2 of these attributes can be set.
    if (style.alignment.Top.is_set && style.alignment.Bottom.is_set && style.alignment.YMiddle.is_set) {
        chart.Error.Error(file_pos.start, "This block has all three of 'top', 'middle' and 'bottom' set. Ignoring all.");
        style.alignment.Top.is_set = style.alignment.Bottom.is_set = style.alignment.YMiddle.is_set = false;
    }
    if (style.alignment.Left.is_set && style.alignment.Right.is_set && style.alignment.XCenter.is_set) {
        chart.Error.Error(file_pos.start, "This block has all three of 'left', 'center' and 'right' set. Ignoring all.");
        style.alignment.Left.is_set = style.alignment.Right.is_set = style.alignment.XCenter.is_set = false;
    }
    if (style.size[0] && style.size[1] && style.aspect_ratio) {
        chart.Error.Error(file_pos.start, "This block has all three of 'width', 'height' (maybe via 'size') and 'aspect_ratio' set. Ignoring 'aspect_ratio'.");
        style.aspect_ratio.reset();
    }
    if (style.aspect_ratio)
        chart.SetXYCorrelation();

    //Remove duplicates from alignment attributes and sizes and around
    StringWithPosList *lists[] = {
        &style.alignment.Left.value.blocks, &style.alignment.Right.value.blocks,
        &style.alignment.Top.value.blocks,  &style.alignment.Bottom.value.blocks,
        &style.alignment.XCenter.value.blocks, &style.alignment.YMiddle.value.blocks,
        style.size[0] ? &style.size[0]->blocks : nullptr, style.size[1] ? &style.size[1]->blocks : nullptr, &around
    };
    for (auto p: lists)
        if (p)
            remove_duplicates(*p, [](auto& a, auto& b) {return a.name==b.name; });

    //Now capture the attributes (before substituting 'first', 'next', etc.) in text for error messages.
    style.alignment.SetTexts();

    if (style.label && style.label->length() && has_visible_content &&
        style.label_pos && style.label_pos==EDirection::Invalid) {
        chart.Error.Error(style.label_pos.file_pos, "For blocks with content inside, the label cannot be in the middle. Switching to 'above'.");
        style.label_pos = EDirection::Above;
    }

    //Now remove 'exact' content alignment if we have a user specified size
    for (unsigned xy = 0; xy<=1; xy++) {
        auto &childalign = (xy ? style.YChildAlign : style.XChildAlign);
        if (!childalign)
            childalign = {EInternalAlignType::Exact, EAlignPrio::Default, FileLineCol()};
        //if we have a minimum size set and we have content, switch from 'exact' to 'center'
        //We pick 'center' to be compatible with the case when no min size is set, but the
        //label is larger in this direction than the content (that is handled as mid in AddConstraints())
        if (has_visible_content  && childalign->align==EInternalAlignType::Exact &&
            style.size[xy] && style.size[xy]->prio != EAlignPrio::Default)
            childalign->align = EInternalAlignType::Mid;
    }

    return ret;
}

void BlockBlock::FinalizeLabel(Canvas &canvas)
{
    //Extract all escapes
    BlockInstruction::FinalizeLabel(canvas);
    //Wrap the label
    if (parsed_label.IsWordWrap())  {
        const bool y = IsVertical(style.label_orient.value_or(EOrientation::Normal));
        if (style.size[y] && style.size[y]->blocks.empty()) {
            //A specific size is given. Now calculate, how much width do we have for the label
            //and how much an added pixel in with would increase the space for the label
            double width, ratio;
            std::tie(width, ratio) = [this, y]()->std::pair<double, double> {
                if (shape>=0) {
                    const Range outer_range(0, style.size[y]->size);
                    const Block content_pos_block = chart.Shapes[shape].GetLabelPos(Block(outer_range, outer_range));
                    const Range &content_pos = content_pos_block[y];
                    return {content_pos.Spans(), content_pos.Spans()/ style.size[y]->size};
                } else {
                    double min_diff, max_diff;
                    std::tie(min_diff, max_diff) = GetInOutDiff(y);
                    return {style.size[y]->size - min_diff - max_diff, 1};
                }
            }();
            Label tmp = parsed_label; //create a copy, so that the second reflow can start from the original
            const double overflow = tmp.Reflow(canvas, chart.Shapes, width);
            if (overflow>1) {
                //try again, we will anyway size the block to as big as needed
                const double overflow2 = parsed_label.Reflow(canvas, chart.Shapes,
                                                             tmp.getTextWidthHeight().x);
                _ASSERT(overflow2<1);
                (void)overflow2; //to stop gcc from complaining
                //chart.Error.Warning(style.label.file_pos, "Too little space for label - may look bad.",
                //                     StrCat("Try increasing ", y ? "height" : "width", " to at least ",
                //                            std::to_string(int(ceil(style.size[y].value+overflow/ratio))), "."));
            } else {
                parsed_label = std::move(tmp);
            }
        } //else we silently ignore the wrap
    }
    available_for_label = GetLabelSize(); //yet unscaled

    for (auto &pChild : content)
        pChild->FinalizeLabel(canvas);
}

BlockBlock *BlockSpace::CloneAs(const FileLineColRange &l, std::string_view name [[maybe_unused]], std::string_view prefix,
								gsl::owner<AlignmentAttr*> align,
								gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                                const MultiBlock* multi_parent [[maybe_unused]]) const
{
    _ASSERT(!multi_parent);
    BlockSpace *ret = new BlockSpace(*this, file_pos.CreatePushed(l, EInclusionReason::COPY), prefix);
    ret->RegisterNames();
    if (align) {
        chart.Error.Error(l.start, "Space commands do not accept alignment modifiers. Ignoring them.");
        delete align;
    }
    if (attr) {
        if (attr->size())
            chart.Error.Error(mod->front()->action_pos.start, "Space commands do not accept attributes. Ignoring them.");
        delete attr;
    }
    if (mod) {
        if (mod->size())
            chart.Error.Error(mod->front()->action_pos.start, "Space commands do not accept content modifiers. Ignoring them.");
        delete mod;
    }
    return ret;
}


bool BlockSpace::PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number)
{
    bool ret = BlockBlock::PostParseProcess(canvas, hide_by, number);

    //Try to determine if we should represent horizontal or vertical space
    bool x = false;
    bool y = false;
    //if any of our attributes contains 'prev' and opposite sides, we stict to that
    if (style.alignment.Bottom.is_set && style.alignment.Bottom.value.blocks.size()==1 &&
        style.alignment.Bottom.value.blocks.front().name=="prev" &&
        style.alignment.Bottom.value.edge.pos==0.0) y = true;
    if (style.alignment.Top.is_set && style.alignment.Top.value.blocks.size()==1 &&
        style.alignment.Top.value.blocks.front().name=="prev" &&
        style.alignment.Top.value.edge.pos==1.0) y = true;
    if (style.alignment.Right.is_set && style.alignment.Right.value.blocks.size()==1 &&
        style.alignment.Right.value.blocks.front().name=="prev" &&
        style.alignment.Right.value.edge.pos==0.0) x = true;
    if (style.alignment.Left.is_set && style.alignment.Left.value.blocks.size()==1 &&
        style.alignment.Left.value.blocks.front().name=="prev" &&
        style.alignment.Left.value.edge.pos==1.0) x = true;

    if (x) style.size[0] = {size>0 ? size : 10, EAlignPrio::Must};
    if (y) style.size[1] = {size>0 ? size : 10, EAlignPrio::Must};
    return ret;
}

BlockBlock *BlockBreak::CloneAs(const FileLineColRange &l, std::string_view name [[maybe_unused]], std::string_view prefix,
								gsl::owner<AlignmentAttr*> align,
								gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                                const MultiBlock* multi_parent [[maybe_unused]]) const
{
    _ASSERT(!multi_parent);
    BlockBreak *ret = new BlockBreak(*this, file_pos.CreatePushed(l, EInclusionReason::COPY), prefix);
    ret->RegisterNames();
    if (align) {
        chart.Error.Error(l.start, "Space commands do not accept alignment modifiers. Ignoring them.");
        delete align;
    }
    if (attr) {
        if (attr->size())
            chart.Error.Error(mod->front()->action_pos.start, "Break commands do not accept attributes. Ignoring them.");
        delete attr;
    }
    if (mod) {
        if (mod->size())
            chart.Error.Error(mod->front()->action_pos.start, "Break commands do not accept content modifiers. Ignoring them.");
        delete mod;
    }
    return ret;
}


bool BlockBreak::PostParseProcess(Canvas & canvas, const BlockBlock* hide_by, Numbering & number)
{
    hidden_by = hide_by;
    if (hidden_by) RemoveFromDrawOrder();
    style.alignment.Empty();
    return BlockBlock::PostParseProcess(canvas, hide_by, number);
}

/** Set the our content to 'c'. Also calculate our style.
 * This is mandatory to be called, even if with nullptr;
 * except for cloned objects, which has their style and content
 * ready - they just need to register their names.
 * @param [in] c The list of blocks to set. Its content will be
 *         removed, but the object itself will not be deleted. May be null.*/
void BlockIndicator::SetContent(std::variant<nullptr_t, BlockInstrList*, StringWithPos> c [[maybe_unused]])
{
    _ASSERT(valid);
    _ASSERT(std::holds_alternative<nullptr_t>(c));
    _ASSERT(parent);
    //We dont RegisterNames();
    orig_has_visible_content = has_visible_content;
    style = parent->indicator_style.read();
    style.type = EStyleType::ELEMENT; //mark us as an element
    style.block_style_type = EBlockStyleType::Unspecified;
    style += parent->running_style.read();
}


bool BlockIndicator::PostParseProcess(Canvas& canvas, const BlockBlock* hide_by, Numbering& number) {
    hidden_by = hide_by;
    if (hidden_by) RemoveFromDrawOrder();
    name_display = StrCat("Indicator of collapsed block '", parent->name_display, "'");
    return BlockInstruction::PostParseProcess(canvas, hide_by, number);
}

void BlockIndicator::Draw(Canvas& canvas) const {
    DrawIndicator(outer_line.Centroid(), &canvas, style.line, style.fill, style.shadow);
}

void BlockInlinedChart::SetContent(std::variant<nullptr_t, BlockInstrList*, StringWithPos> c) {
    valid = false;
    switch (c.index()) {
    case 1:
        if (BlockInstrList *l = std::get<BlockInstrList*>(c))
            chart.Error.Error(l->size() && l->front() ? l->front()->file_pos.start : file_pos.end.NextChar(), 
                              "Instead of blocks enclosed in '{' and '}' symbols, 'chart' blocks must have an inlined chart.",
                              StrCat("Supply the chart text enclosed between the '" INLINE_OPEN_STR "' and '" INLINE_CLOSE_STR"' tokens."));
        break;
    case 0:
        chart.Error.Error(file_pos.end.NextChar(), "'chart' blocks must have an inlined chart.",
                          StrCat("Supply the chart text enclosed between the '" INLINE_OPEN_STR "' and '" INLINE_CLOSE_STR"' tokens."));
        break;
    case 2:
        if (lang.size()) {
            RegisterNames(); //calculates name_gui_collapse, as well
            _ASSERT(chart.languages->GetLanguage(lang));
            auto& [txt, pos] = std::get<StringWithPos>(c); 
            std::string_view gui_state;
            if (auto i = chart.GUI_State.inlined.find(name_gui_collapse); i != chart.GUI_State.inlined.end()) {
                if (i->second.gui_state.empty() || txt.empty())
                    chart.GUI_State.inlined.erase(i);
                else {
                    gui_state = i->second.gui_state;
                    i->second.element_exists = true;
                }
            }
            if (txt.size()) {
                inlining_data.chart = chart.CompileInlineText(lang, {}, txt, pos.start, this, gui_state);
                if (inlining_data.chart) {
                    inlining_data.file_pos = std::move(pos);
                    text = std::move(txt);
                    valid = true;
                    //Arriving to this function the attributes specified by the user on this block are in 'style'. See BlockBlock::SetContent() for a reference of what needs to be done.
                    BlockStyle attributes = std::move(style);
                    style.Empty();
                    style = *BaseStyle(false);
                    style.type = EStyleType::ELEMENT; //mark us as an element
                    style.block_style_type = EBlockStyleType::Unspecified;
                    //We have no refinement style
                    //We cannot be collapsed, so we do not fill alternative_style
                    //We are not 'around' so that is not done either
                    //But we do add the running style for blocks
                    style += chart.MyCurrentContext().running_style_blocks.read();
                    //We have no label - nothing to do there
                    //Ok, re-add the attributes set by the user.
                    style += attributes;
                }
            }
        }
        if (!valid)
            UnRegisterNames();
    }
}

BlockBlock* BlockInlinedChart::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
{
    if (!valid) return nullptr;
    BlockInlinedChart* ret = new BlockInlinedChart(*this, file_pos.CreatePushed(l, EInclusionReason::COPY), name, prefix);
    ret->RegisterNames();
    ret->style.Empty();
    ret->AddAttributeList(attr);
    ret->ApplyCloneModifiers(mod);
    BlockStyle attributes = std::move(ret->style);
    ret->style = style;
    ret->style += attributes;
    //We key the priorities set in 'align' as they are. I expect they are to be 'Explicit' as
    //TranslatePre() generates such alignments.
    if (align) {
        ret->style.alignment.MergeByDimension(*align);
        delete align;
    }
    //Push all attributes
    const FileLineCol inclusion(l.start.TopOfStack());
    ret->style.Push(inclusion);
    ret->lang = lang;
    ret->text = text;
    ret->inlining_data.chart = inlining_data.chart;
    ret->inlining_data.file_pos = inlining_data.file_pos.CreatePushed(l, EInclusionReason::COPY);
    if (multi_parent)
        ret->name_gui_collapse = multi_parent->name_gui_collapse;
    ret->UpdateCollapsedFromGUI();
    return ret;
}

bool BlockInlinedChart::PostParseProcess(Canvas& canvas, const BlockBlock* hide_by, Numbering& number) {
    if (!BlockBlock::PostParseProcess(canvas, hide_by, number))
        return false;
    _ASSERT(inlining_data.chart);
    //If the user has specified none of the width/height attribute, we add them
    const XY wh = inlining_data.chart->GetTotal().Spans();
    if (!style.size[0] && !style.size[1]) {
        //If user has set 'aspect_ratio' but no size, some size may come from
        //top/bottom or left/right settings. We add default sizes with low priority
        //and hope for the layout algo to figure it out.
        style.size[0].emplace(wh.x, EAlignPrio::Default);
        style.size[1].emplace(wh.y, EAlignPrio::Default);
    } else if (!style.size[0] || !style.size[1]) //user specified only one of width or height, keep natural ratio
        if (!style.aspect_ratio) {               //but only if 'aspect_ratio' is not set. If that is set, the other size will be calculated by the layout algo.
            const unsigned xy = style.size[1].has_value() ? 1 : 0; //1 if the height was specified, 0 if width
            _ASSERT(!style.size[1 - xy]);
            style.size[1 - xy].emplace(style.size[xy]->size * wh[1 - xy] / wh[xy], EAlignPrio::Default, true, style.size[xy]->blocks);
            chart.SetXYCorrelation();
        } 
    //else: user specified both width and height (maybe via 'size'), keep them.
    return true;
}

void BlockInlinedChart::PostPosProcess(Canvas& canvas, Chart* ch) {
    if (!valid) return;
    if (inlining_data.chart) {
        const Block mid = outer_line.CreateExpand(-style.line.LineWidth() / 2);
        inlining_data.parent_to_inline
            = TRMatrix::CreateShift(inlining_data.chart->GetTotal().UpperLeft())
            * TRMatrix::CreateScale(XY{ inlining_data.chart->GetTotal().x.Spans() / mid.x.Spans(), inlining_data.chart->GetTotal().y.Spans() / mid.y.Spans() })
            * TRMatrix::CreateShift(-mid.UpperLeft());
        area_draw = area.CreateExpand(chart.trackFrameWidth) - area;
        draw_is_different = true;
        area_draw_is_frame = true;
    }
    BlockBlock::PostPosProcess(canvas, ch);
}

void BlockInlinedChart::Draw(Canvas& canvas) const {
    const Block mid = outer_line.CreateExpand(-style.line.LineWidth() / 2);
    if (canvas.does_graphics()) {
        canvas.Shadow(mid, style.line, style.shadow);
        canvas.Fill(mid, style.line, style.fill);
    } else
        canvas.Add(GSBox(outer_line, LineAttr::None(), style.fill, style.shadow, {}, ETextAlign::Min));
    if (inlining_data.chart) {
        const bool err = canvas.Transform(inlining_data.parent_to_inline.Invert());
        if (err)
            chart.Error.Error(file_pos.start, "Cannot export this inlined chart as it is placed here with sheering or flip transformations and the output format cannot take them.");
        else {
            inlining_data.chart->Draw(canvas, false, 0);
            canvas.UnTransform();
        }
    }
    if (canvas.does_graphics())
        canvas.Line(mid, style.line);
    else {
        canvas.Add(GSBox(outer_line, style.line, FillAttr::None(), ShadowAttr::None(), parsed_label, ETextAlign::Min));
    }
}

void BlockInlinedChart::InlinedChartGUIStateChange() const {
    if (inlining_data.chart) {
        chart.GUI_State.inlined[name_gui_collapse].gui_state = inlining_data.chart->SerializeGUIState();
        chart.GUIStateChanged();
    }
}


/** Resolves alignment attributes to specific blocks and sanitizes them (emits errors).
 * If we are the first or last in the parent block, our name will
 * be equal to 'first' or 'last', resp.
 * Also adjust the alignment attributes of our content based on our 'content.x/y'
 * attributes and calls them recursively.*/
void BlockBlock::ResolveAlignmentAttributes(const BlockBlock *first, const BlockBlock *prev,
                                            const BlockBlock *next, const BlockBlock *last)
{
    //Now translate 'first', 'prev', 'next', 'last' to actual block names.
    //Give error if we do not have 'prev' or 'next' (because we are first, last) or if we are the same.
    //Do not give error if it was not the user that specified the token, but it comes from some default.
    const std::array<OptAlignAttr*, 6> attrs = {
        &style.alignment.Left, &style.alignment.XCenter, &style.alignment.Right,
        &style.alignment.Top,  &style.alignment.YMiddle, &style.alignment.Bottom};
    static const std::array<double, 6> opposite_pos = {1.0, -1000, 0.0, 1.0, -1000, 0.0};
    const SpecBlockPtrs vars = {first, prev, next, last, parent};

    //Resolve block names in alignment attributes
    for (unsigned a = 0; a<6; a++)
        if (attrs[a]->is_set &&
            Resolve(attrs[a]->value.blocks, vars, {},
                    attrs[a]->set_from == EStyleType::ELEMENT ?
                        "Use of '%b' in the %p element in a series is ambiguous. Ignoring attribute." :
                        "",
                    {},
                    &attrs[a]->text,
                    attrs[a]->value.edge.pos==opposite_pos[a] ?
                        &attrs[a]->value.justify :
                        nullptr))
            attrs[a]->is_set = false;
    //OK, handle width and height, too
    for (unsigned a = 0; a<2; a++)
        if (style.size[a] &&
            Resolve(style.size[a]->blocks, vars,
                    "The block '%b' is a 'break' block, which has no size. Ignoring attribute.",
                    "Use of '%b' in the %p element in a series is ambiguous. Ignoring attribute.",
                    {}, nullptr, nullptr))
            style.size[a].reset();



    //Now verify that all attributes refer to valid blocks
    for (unsigned a = 0; a<6; a++)
        if (attrs[a]->is_set &&
            CheckExists(attrs[a]->value.blocks, "attribute", {HandleHidden::ToParent},
                        "This block will align to itself. Is this what you want? Ignoring attribute."))
            attrs[a]->is_set = false;

    for (unsigned a = 0; a<2; a++)
        if (style.size[a] &&
            CheckExists(style.size[a]->blocks, "attribute", { HandleHidden::ToParent },
                        "This block will size to itself. Is this what you want? Ignoring attribute."))
            style.size[a].reset();

    style.MakeCompleteButText();


    //Now check if we are aligning to some far away guy
    //In that case kill X or Y alignment in our parents
    //up to the common parent
    for (unsigned a = 0; a<6; a++)
        if (attrs[a]->is_set)
            //kill attributes in this direction in the block neighboring to a parent of other
            for (auto &sp : attrs[a]->value.blocks) {
                const BlockBlock *other = chart.GetBlockByName(sp.name, parent, true, true)[0];
                auto common = GetCommonParent(other);
                if (common && common!=this && common!=other) {
                    //...in our parent, which is in the same l
                    for (auto p = parent; p!=common; p = p->parent) {
                        _ASSERT(p);
                        if (p->parent!=common) continue;
                        p->KillAlignmentBasedOnChildAttr(a>2, attrs[a]->value.prio);
                        break;
                    }
                    //...and in the referenced block or its parent, neighbouring a parent of us.
                    for (auto p = other->parent; p!=common; p = p->parent) {
                        _ASSERT(p);
                        if (p->parent!=common) continue;
                        p->KillAlignmentBasedOnChildAttr(a>2, attrs[a]->value.prio);
                        break;
                    }
                }
            }
    chart.Progress.DoneBulk(ESections::POSTPROCESS);
    if (content.size()==0) return;
    const auto pFirst = FindApplicableNextAtOrAfter(content.begin());
    const auto pLast = FindApplicablePrevBefore(content.end());
    for (auto i = content.begin(); i!=content.end(); i++)
        (*i)->ResolveAlignmentAttributes(pFirst, FindApplicablePrevBefore(i),
                                         FindApplicableNextAtOrAfter(std::next(i)), pLast);
}


/** Registers the variables needed for this block.
 * If we have been laid out already, we only register the out_min/max variable as the
 * rest is laid out already.
 * @param constraints The set we add our constraints to.
 * @param [in] from if 0, we add the x coordinate variables, if 1, not.
 * @param [in] to if 1, we add the y coordinate variables, if 0 not.
 * Each block has one variable for its outer edge. The ones having content,
 * have one for each side of the content bounding box. Finally those
 * with justified content have one for x and y direction that is the
 * space inserted between the content blocks.
 * Variable indices are enumerated from one. If one of the index_XXX members
 * are set to zero, that means this block does not need that variable.*/
void BlockBlock::AddVariableIndices(ConstraintSet &constraints, unsigned from, unsigned to) const
{
    /* Here are what the variables actually mean.

       :<-index_out_min[0]
       : :<-index_in_min[0]
       : :     :<-index_label_min[0]
       : :     :           :<-index_label_max[0]
       : :     :           :          :<-index_in_max[0]
       : :     :           :          : :<-index_out_max[0]
       : :     :           :          : :______________________<-index_out_min[1]
        *******:***********:************_______________________<-index_in_min[1]
        *      :           :           *
        *      :           :___________*______<-index_label_min[1]
        *      LABELABELABEL___________*______<-index_label_max[1]
        *                              *
        *            __________________*______<-index_content_min[1]
        *     *******                  *
        *     *     *                  *
        *     *******                  *
        *         ^                    *
        *         |<-index_justify[1]  *
        *         v                    *
        *     *****************        *
        *     *               *        *
        *     *****************_______ *______<-index_content_max[1]
        *     :               :        *
        ******:***************:*********
              :               :<-index_content_max[0]
              :<-index_content_min[0]
    */

    //We assume all index_* members are zero here.

    const bool has_label = style.label && style.label->length()>0;
    const bool has_anything = has_visible_content || has_label;
    if (from==0) {
        index_out_min[0] = constraints.AddVariable(this, "left");
        if (has_anything)
            index_in_min[0] = constraints.AddVariable(this, "left_in");
    }
    if (to==1) {
        index_out_min[1] = constraints.AddVariable(this, "top");
        if (has_anything)
            index_in_min[1] = constraints.AddVariable(this, "top_in");
    }

    if (has_visible_content  && from==0)
        index_content_min[0] = constraints.AddVariable(this, "content_left");
    if (has_visible_content  && to==1)
        index_content_min[1] = constraints.AddVariable(this, "content_top");

    if (has_visible_content  && has_label && from==0)
        index_label_min[0] = constraints.AddVariable(this, "label_left");
    if (has_visible_content  && has_label && to==1)
        index_label_min[1] = constraints.AddVariable(this, "label_top");

    for (auto& pChild : content)
        pChild->AddVariableIndices(constraints, from, to);

    if (from == 0 && has_visible_content  && style.XChildAlign &&
        style.XChildAlign->align == EInternalAlignType::Spread)
        index_justify[0] = constraints.AddVariable(this, "justify_x");

    if (to == 1 && has_visible_content  && style.YChildAlign &&
        style.YChildAlign->align == EInternalAlignType::Spread)
        index_justify[1] = constraints.AddVariable(this, "justify_y");

    if (has_visible_content  && from==0)
        index_content_max[0] = constraints.AddVariable(this, "content_right");
    if (has_visible_content  && to==1)
        index_content_max[1] = constraints.AddVariable(this, "content_bottom");

    if (has_visible_content  && has_label && from==0)
        index_label_max[0] = constraints.AddVariable(this, "label_right");
    if (has_visible_content  && has_label && to==1)
        index_label_max[1] = constraints.AddVariable(this, "label_bottom");

    if (from==0) {
        if (has_anything)
            index_in_max[0] = constraints.AddVariable(this, "right_in");
        index_out_max[0] = constraints.AddVariable(this, "right");
    }
    if (to==1) {
        if (has_anything)
            index_in_max[1] = constraints.AddVariable(this, "bottom_in");
        index_out_max[1] = constraints.AddVariable(this, "bottom");
    }
}

std::pair<unsigned, unsigned>
BlockBlock::AddMinMaxConstraint(ConstraintSet &constraints, const StringWithPosList &list, double offset,
                                bool margin, bool y,
                                bool do_min, bool do_max) const
{
    std::pair<unsigned, unsigned> ret = {0,0};
    if (do_min) {
        ret.first = constraints.AddVariable(this, "attribute min");
        std::vector<VariableOffset> vec;
        vec.reserve(list.size());
        for (auto &s : list) {
            auto v = chart.GetBlockByName(s.name, parent, true, true);
            _ASSERT(v[0] && v[1]==nullptr);
            const auto p = v[0];
            vec.push_back({p->index_out_min[y], (margin ? -p->style.min_margin[y].value_or(0) : 0) + offset});
        }
        constraints.AddConstrMinOf(this, ret.first, std::move(vec));
    }
    if (do_max) {
        ret.second = constraints.AddVariable(this, "attribute max");
        std::vector<VariableOffset> vec;
        vec.reserve(list.size());
        for (auto &s : list) {
            auto v = chart.GetBlockByName(s.name, parent, true, true);
            _ASSERT(v[0] && v[1]==nullptr);
            const auto p = v[0];
            vec.push_back({p->index_out_max[y], (margin ? p->style.max_margin[y].value_or(0) : 0) + offset});
        }
        constraints.AddConstrMaxOf(this, ret.second, std::move(vec));
    }
    return ret;
}


/** Fills in c.s2 to the side referring to the value of an alignment attribute,
 * and 'type', 'prio', 'attr_pos', 'display_prio' members of c.
 * We assume all blocknames are resolved and valid. Else we _ASSERT and GLPK fails
 * @param constraints The set we add our constraints to.
 * @param [out] c The Constraint we fill.
 * @param [in] a The Alignment attribute we talk about. If not set, we silently return
 *               an invalid side.
 * @param [in] y True if this is an attribute in the y direction, false if in x.
 * @returns true if the attribute is set and c.s2 is filled. False if the attribute is not set.*/
bool BlockBlock::FillTargetSide(ConstraintSet &constraints, Constraint &c, const OptAlignAttr &a, bool y) const
{
    if (!a.is_set || a.value.blocks.size()==0) return false;
    c.type = Constraint::EQUAL;
    c.attr_pos = a.value.blocks.front().file_pos.start;
    c.prio = a.value.prio;
    c.display_prio = a.value.prio;
    unsigned var_min, var_max;
    double margin_min=0, margin_max=0;
    if (a.value.blocks.size()==1) {
        auto v = chart.GetBlockByName(a.value.blocks.front().name, parent, true, true);
        _ASSERT(v[0] && v[1]==nullptr);
        const auto p = v[0];
        if (p==parent) {
            if (a.value.edge.margin) {
                var_min = p->index_in_min[y];
                var_max = p->index_in_max[y];
            } else {
                var_min = p->index_out_min[y];
                var_max = p->index_out_max[y];
            }
        } else {
            var_min = p->index_out_min[y];
            var_max = p->index_out_max[y];
            if (a.value.edge.margin) {
                margin_min = -p->style.min_margin[y].value_or(0);
                margin_max = +p->style.max_margin[y].value_or(0);
            }
        }
    } else {
        std::tie(var_min, var_max) =
            AddMinMaxConstraint(constraints, a.value.blocks, 0 /* offset handled below*/, a.value.edge.margin,
                                y, a.value.edge.pos!=1.0, a.value.edge.pos!=0.0);
        //We keep margin_{min,max} zero.
    }
         if (a.value.edge.pos==0.0) c.s2 = SSide(var_min, a.value.edge.offset + margin_min);
    else if (a.value.edge.pos==1.0) c.s2 = SSide(var_max, a.value.edge.offset + margin_max);
    else c.s2 = WSumSide(var_min, var_max, 1-a.value.edge.pos, a.value.edge.pos,
						 margin_min*(1-a.value.edge.pos)+margin_max*a.value.edge.pos +
						     a.value.edge.offset);
    return true;
}

/** Returns the difference between index_out_min/max and index_in_min/max assuming we are a box.
 * We define the difference between the inner and outer margins
   as imargin+lw. If the corner is bevel or round, we may increase
   imargin to cater for it. We do this for shapes, too, but there will be other
   considerations for them. */
std::pair<double, double> BlockBlock::GetInOutDiff(bool y) const
{
    //We define the difference between the inner and outer margins
    //as imargin+lw. If the corner is bevel or round, we may increase
    //imargin to cater for it.
    double min_diff;
    const auto effective_corner = shape>=0 ?
        ECornerType::NONE :
        style.line.corner.value_or(ECornerType::NONE);
    switch (effective_corner) {
    default:
        min_diff = 0;
        break;
    case ECornerType::BEVEL:
        min_diff = std::max(0., style.line.radius.value_or(0.0))/2;
        break;
        case ECornerType::
            ROUND: min_diff = std::max(0., style.line.radius.value_or(0.0))/sqrt(2);
            break;
    }
    min_diff += style.f_line ? style.line.LineWidth() : 0;
    double max_diff = min_diff;
    min_diff = std::max(min_diff, style.min_imargin[y].value_or(0));
    max_diff = std::max(max_diff, style.max_imargin[y].value_or(0));
    return {min_diff, max_diff};
}


/** Creates the constraints based on our attributes and adds them to the chart.
 * If any Constraint added has an undefined variable in it, then the set of
 * constraints will be incomplete - we terminate early. That means that a block
 * is laid out where some alignment attribute (of it or of its children) references
 * a block that is not a child of the block being laid out. Such block cannot be
 * laid out alone, so we fail.
 * @param [in] canvas The canvas to get text dimensions.
 * @param constraints The set we add our constraints to.
 * @param [in] from if 0, we handle the x coordinates, if 1, not.
 * @param [in] to if 1, we handle the y coordinates, if 0 not.
 * @param [in] parent_index_justify Contains the variables the parent uses
 *                                  for justification. [0] for x coordinates,
 *                                  [1], only filled if from/to is appropriate.
 *                                   -1 if no justification going on.
 * @param [in] parent_attr_justify Pointer to the justify attribute of the parent.
 *                                 Ignore if no justification. Used to set
 *                                 Constraint::justify_attr_pos.
 * @returns true (for each direction if from!=to) if we have inserted justify spacings,
 *          plus zero, as blocks don't really have coordinates relative to their parent.*/
BlockInstruction::AddConstraintResult
BlockBlock::AddConstraints(Canvas & canvas, ConstraintSet &constraints, unsigned from, unsigned to,
                           unsigned parent_index_justify[2],
                           const InternalAlignment *parent_attr_justify[2]) const
{
    AddConstraintResult ret;
    AddConstraintResult children_info;
    //0. Add Constraints for children.
    if (content.size()) {
        const InternalAlignment* attr_justify[2] = { &*style.XChildAlign, &*style.YChildAlign };
        for (auto& pChild: content)
            children_info += pChild->AddConstraints(canvas, constraints, from, to, index_justify, attr_justify);
    }

    if (style.aspect_ratio) {
        if (from == to)
            chart.Error.Error(file_pos.start, "Internal error: 'aspect_ratio' set, but we calculate X and Y dimensions separately. Ignoring this attribute.");
        else 
            constraints.AddConstrEqual(this, "size equal to another block", EAlignPrio::Explicit,
                                       WSumSide(index_out_min[0], index_out_max[0], -1, +1),
                                       WSumSide(index_out_min[1], index_out_max[1], -*style.aspect_ratio, *style.aspect_ratio));
    }

    const bool has_visible_label = 0 < available_for_label.x && 0 < available_for_label.y;

    for (unsigned xy = from; xy<=to; xy++) {
        //0b. Add constraint to have at least as big inner space as needed by children
        if (children_info[xy].max_to_imargin > 0)
            constraints.AddConstrGrEq(this, "content inner size", EAlignPrio::Must, false,
                                      SSide(index_in_max[xy]), SSide(index_in_min[xy], children_info[xy].max_to_imargin));
        //We shall act below for any arrows reporting an extent relative to the visible contour.
        //We may have no other content and in that case we will fall back to the size.
        //So here acting only if the max_to_visible > imargin_min+max is not enough.
        if (children_info[xy].max_to_visible > 0)
            constraints.AddConstrGrEq(this, "content outer size", EAlignPrio::Must, false,
                                      SSide(index_in_max[xy]), SSide(index_in_min[xy], children_info[xy].max_to_visible
                                                                     - *style.min_imargin[xy]
                                                                     - *style.max_imargin[xy]));
		//1. Add external and internal margins - we will substract the external ones only, when
        //filling in outer_line
        const auto [min_diff, max_diff] = GetInOutDiff(xy);
        //Only if we have anything inside, do we also have in-variables
        if (has_visible_label || has_visible_content) {
			if (shape<0) {
                constraints.AddConstrEqual(this, "min margin", EAlignPrio::Must,
                                           SSide(index_in_min[xy]), SSide(index_out_min[xy], min_diff));
                constraints.AddConstrEqual(this, "max margin", EAlignPrio::Must,
                                           SSide(index_out_max[xy]), SSide(index_in_max[xy], max_diff));
			} else {
                //For shapes the label pos is not to a fixed offset to the outer_line, but relative:
                //the distance between outer line and label pos grows if the shape is resized larger.
                //Here we take the label pos when the outer_line is (0,0,1,1)box;
                const Block content_pos_block = chart.Shapes[shape].GetLabelPos(Block(0, 1, 0, 1));
                const Range &content_pos = content_pos_block[xy];
                //The relation is as follows:
                // outer_min + label_min*size == inner_min
                // outer_min + label_max*size == inner_max
                // size := outer_max - outer_min
				// and label_min/max is interpreted between [0..1]
				//Thus
                // outer_min*(1-label_min) + outer_max*label_min == inner_min
                // outer_min*(1-label_max) + outer_max*label_max == inner_max
				//Finally, if we want to have imargin
				// outer_min*(1-label_min) + outer_max*label_min == index_in_min - imargin_min
				// outer_min*(1-label_max) + outer_max*label_max == index_in_max + imargin_max
                constraints.AddConstrEqual(this, "min margin shape", EAlignPrio::Must,
                                           SSide(index_in_min[xy], -style.min_imargin[xy].value_or(0)),
                                           WSumSide(index_out_max[xy], index_out_min[xy], content_pos.from));
                constraints.AddConstrEqual(this, "max margin shape", EAlignPrio::Must,
                                           SSide(index_in_max[xy], +style.max_imargin[xy].value_or(0)),
                                           WSumSide(index_out_max[xy], index_out_min[xy], content_pos.till));
            }
        }
        //2. Post a constraint on our label
        //Additional constraints will come when defining the content's
        //relation to us below.
        if (has_visible_content && has_visible_label) {
            constraints.AddConstrEqual(this, "label size", EAlignPrio::Must,
                                       SSide(index_label_max[xy]),
                                       SSide(index_label_min[xy], available_for_label[xy]));

        } else if (has_visible_label) {
            //We only have a label. If we have explicit size set, that is a concrete number,
            //but size_mode is not at_least, then we do not set a label constraint, since
            //solely the size attr will determine our size.
            if (!style.size[xy] || style.size[xy]->blocks.size() ||
                style.label_mode.value_or(ELabelMode::ENLARGE)==ELabelMode::ENLARGE)
                //have size at least this big.
                //We dont use equal here, since the user may have specified an explicit size, that is handled below.
                constraints.AddConstrGreater(this, "label", EAlignPrio::Explicit_high, //explicit, because user wants it inside
                                             SSide(index_in_max[xy]),
                                             SSide(index_in_min[xy], available_for_label[xy]));
        }
        //3. Add constraint on our minimum size
        if (style.size[xy]) {
            if (style.size[xy]->blocks.empty()) {
                //A specific value is set, not reference to blocks.
                //If we have no content, any width/height attribute is a must.
                //Else it is a minimum value
                if (!has_visible_content) {
                    //We calculate the relation of the user specified width/height (for the outer edge)
                    //to the label size (inner margin) differently for boxes and shapes
                    //User defined size
                    const double user_size = style.size[xy]->size;
                    //This will be the size based on the label
                    double size_by_label = available_for_label[xy]; //we start with natural text size. This is yet unscaled
                    //This is how much space would be available for the label if we adhere
                    //strictly to user defined size.
                    double av_for_label = style.size[xy]->size; //we start with the user defined size
                    //label must be inside the inner margins, add the difference between the two
                    if (shape<0) {
                        size_by_label += min_diff + max_diff;
                        av_for_label -= min_diff + max_diff;
                    } else {
                        const Block content_pos_block = chart.Shapes[shape].GetLabelPos(Block(0, 1, 0, 1));
                        const Range &content_pos = content_pos_block[xy];
                        if (content_pos.IsInvalid()) {
                            size_by_label = 0;
                            av_for_label = 0;
                        } else {
                            //TODO: This is in conflict with the calculations above.
                            size_by_label = size_by_label/content_pos.Spans();
                            av_for_label *= content_pos.Spans();
                        }
                    }
                    //OK, determine the final size, based on size_mode
                    //and set the scaled label size (if we need to scale)
                    double final_size;
                    switch (style.label_mode.value_or(ELabelMode::ENLARGE)) {
                        default: _ASSERT(0); FALLTHROUGH;
                        case ELabelMode::ENLARGE:
                            //If we have at_least, set to the max of the two
                            final_size = std::max(size_by_label, user_size);
                            break; //leave av4label unscaled
                        case ELabelMode::SCALE:
                        case ELabelMode::SCALE_2D:
                            //For exact, ignore label_size
                            final_size = user_size;
                            available_for_label[xy] = av_for_label;
                    }
                    constraints.AddConstrEqual(this, "exact size", style.size[xy]->prio,
                                               SSide(index_out_max[xy]),
                                               SSide(index_out_min[xy], final_size));
                } else {
                    constraints.AddConstrGreater(this, "min size", style.size[xy]->prio,
                                                 SSide(index_out_max[xy]),
                                                 SSide(index_out_min[xy], style.size[xy]->size));
                }
            } else {
                unsigned var_min, var_max;
                unsigned use_xy = xy;
                if (style.size[xy]->other_dim_of_blocks) {
                    if (from == to) {
                        std::string err = StrCat("Internal error: The ", xy ? "height" : "width", " of this block depends on the ", xy ? "width" : "height",
                                                 " of block(s): ");
                        for (auto& b : style.size[xy]->blocks)
                            err.append(b.name).append(", ");
                        err.pop_back();
                        err.back() = '.';
                        chart.Error.Error(style.size[xy]->blocks.front().file_pos.start, err);
                    } else
                        use_xy = 1 - xy;
                }
                if (style.size[xy]->blocks.size()==1) {
                    //One block is specified for size
                    auto v = chart.GetBlockByName(style.size[xy]->blocks.front().name, parent, true, true);
                    _ASSERT(v[0] && v[1]==nullptr);
                    const auto b = v[0];
                    var_min = b->index_out_min[use_xy];
                    var_max = b->index_out_max[use_xy];
                } else {
                    //Several blocks are specified for size.
                    //We always take their size with margin
                    std::tie(var_min, var_max) =
                        AddMinMaxConstraint(constraints, style.size[xy]->blocks, 0, false,
                                            use_xy, true, true);
                }
				//style.size[xy].value contains any scaling (lets call it 'v')
                //What we want is out_max-out_min == (var_max-var_min)*v.
                //Rearranging: 1*out_max + v*var_min  ==  1*out_min + v*var_max
                constraints.AddConstrEqual(this, "size equal to another block", style.size[xy]->prio,
                                            WSumSide(index_out_max[xy], var_min, 1, style.size[xy]->size),
                                            WSumSide(index_out_min[xy], var_max, 1, style.size[xy]->size));
            }
        }

        //4. Add the relation between our content and the inner margin
        if (has_visible_content) {
            InternalAlignment childalign = (xy ? style.YChildAlign : style.XChildAlign).value_or(InternalAlignment{});
            const EDirection label_dir = style.label_pos.value_or(EDirection::Above);
            //4a. constraints for content variables to be min/max of actual content
            std::vector<VariableOffset> minvec, maxvec;
            minvec.reserve(content.size()+around.size()+1);
            maxvec.reserve(content.size()+around.size()+1);
            const bool m = style.content_margin.value_or(true);
            for (auto &pChild : content) {
                const BlockBlock *pBlock = dynamic_cast<BlockBlock *>(pChild.get());
                if (!pBlock) continue;
                minvec.push_back({pBlock->index_out_min[xy],
                                    m ? -pBlock->style.min_margin[xy].value_or(0) : 0 });
                maxvec.push_back({pBlock->index_out_max[xy],
                                    m ? +pBlock->style.max_margin[xy].value_or(0) : 0 });
            }
            for (auto &b : around) {
                auto pChild = chart.GetBlockByName(b.name, parent, true, true)[0];
                minvec.push_back({pChild->index_out_min[xy],
                                    m ? -pChild->style.min_margin[xy].value_or(0) : 0});
                maxvec.push_back({pChild->index_out_max[xy],
                                    m ? +pChild->style.max_margin[xy].value_or(0) : 0});
            }
            constraints.AddConstrMinOf(this, index_content_min[xy], std::move(minvec));
            constraints.AddConstrMaxOf(this, index_content_max[xy], std::move(maxvec));

            //4b. place content into inner boundaries
            constraints.AddConstrGreater(this, "content inside", EAlignPrio::Must,
                                         SSide(index_content_min[xy]), SSide(index_in_min[xy]));
            constraints.AddConstrGreater(this, "content inside", EAlignPrio::Must,
                                         SSide(index_in_max[xy]), SSide(index_content_max[xy]));

            //These 2 indices below will be the ones to align content to.
            //Normally they are the internal margins, but label may modify it
            unsigned min_index = index_in_min[xy];
            unsigned max_index = index_in_max[xy];
            double min_offset = 0;
            double max_offset = 0;
            //4c. Handle the label's position relative to content
            if (has_visible_label) {
                if (IsVertical(label_dir) == (xy==1)) {
                    if (IsMin(label_dir)) {
                        min_index = index_label_max[xy];
                        min_offset = *style.label_imargin;
                        //align label top
                        constraints.AddConstrEqual(this, xy ? "top label alignment" : "left label alignment",
                                                   style.label_pos.file_pos, EAlignPrio::Must, //TODO: this must come from label_pos instead
                                                   SSide(index_in_min[xy]), SSide(index_label_min[xy]));
                        //make label left/above content
                        constraints.AddConstrGreater(this, xy ? "top label alignment" : "left label alignment",
                                                     style.label_pos.file_pos, EAlignPrio::Must, 
                                                     SSide(index_content_min[xy]), SSide(min_index, min_offset));
                    } else {
                        max_index = index_label_min[xy];
                        max_offset = -*style.label_imargin;
                        constraints.AddConstrEqual(this, xy ? "bottom label alignment" : "right label alignment",
                                                   style.label_pos.file_pos, EAlignPrio::Must, //TODO: this must come from label_pos instead
                                                   SSide(index_in_max[xy]), SSide(index_label_max[xy]));
                        constraints.AddConstrGreater(this, xy ? "bottom label alignment" : "right label alignment",
                                                     style.label_pos.file_pos, EAlignPrio::Must, 
                                                     SSide(max_index, max_offset), SSide(index_content_max[xy]));
                    }
                } else {
                    //now align the label left/center/right
                    const double la = style.label_align.value_or(0.5);
                    if (la==0)
                        constraints.AddConstrEqual(this, "label minalign", EAlignPrio::Must,
                                                   SSide(index_label_min[xy]), SSide(index_in_min[xy]));
                    else if (la==1)
                        constraints.AddConstrEqual(this, "label maxalign", EAlignPrio::Must,
                                                   SSide(index_label_max[xy]), SSide(index_in_max[xy]));
                    else
                        constraints.AddConstrEqual(this, "label midalign", EAlignPrio::Must,
                                                   WSumSide(index_label_max[xy], index_label_min[xy], la),
                                                   WSumSide(index_in_max[xy], index_in_min[xy], la));
                }
                //make the label inside the content
                constraints.AddConstrGreater(this, "label inside", EAlignPrio::Must,
                                             SSide(index_label_min[xy]), SSide(index_in_min[xy]));
                constraints.AddConstrGreater(this, "label inside", EAlignPrio::Must,
                                             SSide(index_in_max[xy]), SSide(index_label_max[xy]));
            }
            //4d. Handle the contents position inside us
            //If our children had no justify inserted, switch 'justify' to 'center'
            if (!children_info[xy].justify_with_neighbour && childalign.align==EInternalAlignType::Spread)
                childalign.align = EInternalAlignType::Mid;
            switch (childalign.align) {
            case EInternalAlignType::Min: //left/top aligned
                constraints.AddConstrEqual(this, xy ? "top alignment" : "left alignment",
                                           childalign.file_pos, childalign.prio,
                                           SSide(index_content_min[xy]), SSide(min_index, min_offset));
                break;
            case EInternalAlignType::Max: //right/bottom aligned
                constraints.AddConstrEqual(this, xy ? "right alignment" : "bottom alignment",
                                           childalign.file_pos, childalign.prio,
                                           SSide(index_content_max[xy]), SSide(max_index, max_offset));
                break;
            case EInternalAlignType::Spread:
                _ASSERT(index_justify[xy]>0);
                constraints.AddConstrEqual(this, xy ? "top content justify" : "left content justify",
                                           childalign.file_pos, childalign.prio,
                                           SSide(index_content_min[xy]), SSide(min_index, min_offset));
                constraints.AddConstrEqual(this, xy ? "bottom content justify" : "right content justify",
                                           childalign.file_pos, childalign.prio,
                                           SSide(index_content_max[xy]), SSide(max_index, max_offset));
                break;
            case EInternalAlignType::Exact: //right/bottom aligned
                if (has_visible_label && IsVertical(label_dir) != (xy==1)) {
                    constraints.AddConstrMinOf(this, min_index, {{index_content_min[xy], -min_offset}, {index_label_min[xy], -min_offset}},
                                               xy ? "top exact content fit" : "left exact content fit",
                                               childalign.file_pos, childalign.prio);
                    constraints.AddConstrMaxOf(this, max_index, {{index_content_max[xy], -max_offset}, {index_label_max[xy], -max_offset}},
                                               xy ? "bottom exact content fit" : "right exact content fit",
                                               childalign.file_pos, childalign.prio);
                } else {
                    constraints.AddConstrEqual(this, xy ? "top exact content fit" : "left exact content fit",
                                               childalign.file_pos, childalign.prio,
                                               SSide(index_content_min[xy]), SSide(min_index, min_offset));
                    constraints.AddConstrEqual(this, xy ? "bottom exact content fit" : "right exact content fit",
                                               childalign.file_pos, childalign.prio,
                                               SSide(index_content_max[xy]), SSide(max_index, max_offset));
                    break;
                }
                FALLTHROUGH; //for the case when we have a label - make content also centered, in case the label is wider
            case EInternalAlignType::Mid: //centered
                constraints.AddConstrEqual(this, xy ? "y middle alignment" : "x center alignment",
                                           childalign.file_pos, childalign.prio,
                                           SumSide(index_content_min[xy], index_content_max[xy]),
                                           SumSide(min_index, max_index, min_offset+max_offset));
                break;
            }
        }

        //5. Add a constraint that is already there on the Must level and orders min->max relation
        //This makes the must constraints solvable by themselves
        constraints.AddConstrGreater(this, "minmax ordering", EAlignPrio::Must,
                                     SSide(index_out_max[xy]), SSide(index_out_min[xy]));
        //6. Add an optimization objective to minimize object size
        constraints.AddConstrMinimize(this, index_out_max[xy], index_out_min[xy]);

        //7. Place constraints on our outer boundaries relative to others.
        //This is where we cater for our alignment attributes
        if (Constraint c(this); FillTargetSide(constraints, c, style.alignment.GetMin(xy), xy)) {
            c.s1 = SSide(index_out_min[xy]);
            c.attr_text = style.alignment.GetMin(xy).text;
            _ASSERT(c.attr_text.length());
            if (style.alignment.GetMin(xy).is_margin && style.min_margin[xy])
                c.s1.offset = -*style.min_margin[xy];
            if (style.alignment.GetMin(xy).value.justify) {
                if (parent_index_justify[xy]>0) {
                    _ASSERT(c.s2.w1==1);   //If we set the justify flag, we must have been prev or next & not mid
                    _ASSERT(c.s2.w2==0);  //If we set the justify flag, we must have been prev or next & not mid
                    c.s2.v2 = parent_index_justify[xy];
                    c.s2.w2 = 1;
                    c.type = Constraint::EQUAL;
                    c.justify_attr_pos = parent_attr_justify[xy]->file_pos;
                    c.prio = parent_attr_justify[xy]->prio;
                    ret[xy].justify_with_neighbour = true;
                } else if (c.prio<=EAlignPrio::Running_style_high) {
                    Constraint c2(c);
                    c2.type = Constraint::INEQUAL;
                    c2.prio = HighPriority(c.prio);
                    //c2.prio = EAlignPrio::Must;
                    c2.attr_text= "default block ordering (min side)";
                    constraints.AddConstraint(std::move(c2));
                }
            }
            constraints.AddConstraint(std::move(c));
        }
        if (Constraint c(this); FillTargetSide(constraints, c, style.alignment.GetMid(xy), xy)) {
            c.s1 = WSumSide(index_out_min[xy], index_out_max[xy], 0.5); //average of the two variables
            c.attr_text = style.alignment.GetMid(xy).text;
            _ASSERT(c.attr_text.length());
			if (style.alignment.GetMid(xy).is_margin)
				c.s1.offset = (style.max_margin[xy].value_or(0)-style.min_margin[xy].value_or(0))/2;
            constraints.AddConstraint(std::move(c));
        }
        if (Constraint c(this); FillTargetSide(constraints, c, style.alignment.GetMax(xy), xy)) {
            if (style.alignment.GetMax(xy).value.justify && parent_index_justify[xy]>0) {
                c.s1 = SumSide(index_out_max[xy], unsigned(parent_index_justify[xy]));
                c.type = Constraint::EQUAL;
                c.justify_attr_pos = parent_attr_justify[xy]->file_pos;
                c.prio = parent_attr_justify[xy]->prio;
                ret[xy].justify_with_neighbour = true;
            } else
                c.s1 = SSide(index_out_max[xy]);
            if (style.alignment.GetMax(xy).is_margin && style.max_margin[xy])
                c.s1.offset = *style.max_margin[xy];
            c.attr_text = style.alignment.GetMax(xy).text;
            _ASSERT(c.attr_text.length());
            if (style.alignment.GetMax(xy).value.justify && parent_index_justify[xy]==0 &&
                c.prio<=EAlignPrio::Running_style_high) {
                Constraint c2(c);
                c2.type = Constraint::INEQUAL;
                std::swap(c2.s1, c2.s2);
                c2.prio = HighPriority(c.prio);
                //c2.prio = EAlignPrio::Must;
                c2.attr_text = "default block ordering (max side)";
                constraints.AddConstraint(std::move(c2));
            }
            constraints.AddConstraint(std::move(c));
        }
    }
    return ret;
}

/** Helper class to solve the layout of the diagram using GLPK.*/
class block::GLPKSolver : public ConstraintSet {
protected:
    static constexpr unsigned passes_must = 5;  ///<How many times we try to resolve a failed MIN/MAX constraint of Must prio
    static constexpr unsigned passes_other = 2; ///<How many times we try to resolve a failed MIN/MAX constraint of lower than Must prio
    glp_prob *lp = nullptr;       ///<The GLPK solver object
    unsigned added_end = 0;       ///<Contains how many in BlockChart::Constraints have been added to 'lp'
    unsigned no_passes = 0;       ///<Counts how many passes we have done
    unsigned no_passes_4xtra = 0; ///<How many additional passes have we done to report more info on conflicts
    unsigned no_removed_constraints = 0; ///<The number of constraints removed in Prune().
    unsigned no_GLPK_variables;   ///<The number of GLPK variables we need. Set in Prune().

    bool Prune();

    unsigned SolveMust();
    void InvalidateContstraint(unsigned constraint);

    template<size_t SIZE>
    double AddConstraintSide(const Constraint::Side &s, bool left, size_t &p,
                             std::array<int, SIZE> &index, std::array<double, SIZE> &value);
    void AddConstraint(const Constraint &c);
    void SetConstrainsTill(unsigned to);
    bool SolveOne(int recurse);

    double GetVal(unsigned a) const { return glp_get_col_prim(lp, Variables[a].mapped_to.var) + Variables[a].mapped_to.off; }
    double GetVal(const VariableOffset &a) const { return GetVal(a.var) + a.offset; }

public:
    const EAlignPrio prio_for_alt;///<We generate alternative constraints to remove at and above this prio level
    const size_t total_units;   ///<Number of progress units we need to report when done.
    BlockProgress &Progress;      ///<We report progress here
    GLPKSolver(EAlignPrio prio, size_t n, BlockProgress &p)
        : no_GLPK_variables(unsigned(Variables.size()-1))
        , prio_for_alt(prio), total_units(n), Progress(p) {}
    ~GLPKSolver() { if (lp) glp_delete_prob(lp); }

    bool Initialize() {
        if (Prune()) return true;
        lp = glp_create_prob();
        glp_set_prob_name(lp, "block layout");
        glp_set_obj_dir(lp, GLP_MIN);
        _ASSERT(no_GLPK_variables);
        glp_add_cols(lp, no_GLPK_variables);
        for (unsigned u = 1; u<no_GLPK_variables+1; u++) {
            glp_set_col_bnds(lp, u, GLP_LO, 0.0, 0.0); //only the lower bound counts
            glp_set_obj_coef(lp, u, 0); //remove all Variables from the objective
        }
        added_end = 0;
        return false;
    }

    //Forces the variable to be nonnegative. Apply to topmost and/or leftmost.
    void ForceVariableToZero(unsigned var) {
        //Pin the GLPK variable to -offset - so that the external variable ends up zero
        glp_set_col_bnds(lp, Variables[var].mapped_to.var, GLP_FX,
                         -Variables[var].mapped_to.off, -Variables[var].mapped_to.off);
    }

    bool Solve();

    LayoutStat EmitErrors(MscError &Error);
    double GetVarValue(unsigned u) const noexcept { return  Variables[u].Get(); }
};

/** Adds variables and their coefficients representing this side of a constraint
 * to variable & coefficient arrays. We assume each variable of a Constraint
 * appears only at one of the sides, so here we do not check, just append.
 * @returns the constant part of the constraint, signed: the offset of the right
 * side is negated.*/
template <size_t SIZE>
double GLPKSolver::AddConstraintSide(const Constraint::Side &s, bool left,  size_t &p,
                                     std::array<int, SIZE> &index, std::array<double, SIZE> &value)
{

    const double coeff = left ? 1 : -1;
    double offset = coeff*s.offset;
    if (s.w1) {
        _ASSERT(s.v1);
        index[p] = Variables[s.v1].mapped_to.var; value[p] = coeff*s.w1; p++;
        offset += coeff*s.w1*Variables[s.v1].mapped_to.off;
    }
    if (s.w2) {
        _ASSERT(s.v2);
        index[p] = Variables[s.v2].mapped_to.var; value[p] = coeff*s.w2; p++;
        offset += coeff*s.w2*Variables[s.v2].mapped_to.off;
    }
    return offset;
}

void GLPKSolver::AddConstraint(const Constraint &c)
{
    if (c.valid != Constraint::EValidity::Valid) {
        c.start_row = -1;
        return;
    }
    switch (c.type) {
    case Constraint::DIFF_MIN:
        //minimize s1-s2. Effective add "s1-s2" to the objective function
        //We use a very high coefficient, to trump any vector minimizaton one below.
        glp_set_obj_coef(lp, Variables[c.s1.v1].mapped_to.var, +1000);
        glp_set_obj_coef(lp, Variables[c.s2.v1].mapped_to.var, -1000);
        return;
    case Constraint::EQUAL:
    case Constraint::INEQUAL: {
        std::array<int, 5> index;
        std::array<double, 5> value;
        size_t p = 1;
        const double off = AddConstraintSide(c.s1, true, p, index, value) +
                           AddConstraintSide(c.s2, false, p, index, value);
        int row = glp_add_rows(lp, 1);
        c.start_row = row;
        glp_set_mat_row(lp, row, int(p)-1, index.data(), value.data());
        glp_set_row_bnds(lp, row, c.type==Constraint::EQUAL ? GLP_FX : GLP_LO, -off, -off);
        return;
    }
    case Constraint::VEC_MAX_EQUAL:
    case Constraint::VEC_MIN_EQUAL:
        if (c.vec.size()==0) return; //nothing to add
        Constraint c_loc;
        Constraint::Side &s1 = c.type==Constraint::VEC_MAX_EQUAL ? c_loc.s1 : c_loc.s2;
        Constraint::Side &s2 = c.type==Constraint::VEC_MAX_EQUAL ? c_loc.s2 : c_loc.s1;
        c_loc.prio = c.prio;
        c_loc.block = c.block;
        c_loc.attr_pos = c.attr_pos;
        c_loc.attr_text = c.attr_text;
        s1 = SSide(c.s1.v1);
        if (c.vec.size()==1) {
            //Add one equality constraint
            c_loc.type = Constraint::EQUAL;
            s2 = SSide(c.vec.front());
            AddConstraint(c_loc);
            c.start_row = c_loc.start_row;
            return;
        }
        //Add one row we use later. Do it now so that we can store its number
        const int row = glp_add_rows(lp, 1);
        c.start_row = row;
        //Add a lot of inequality constraints
        c_loc.type = Constraint::INEQUAL;
        for (auto &u : c.vec) {
            s2 = SSide(u);
            AddConstraint(c_loc);
        }


        //Add one variable to minimize and one constraint for it
        //Add the min or the negative of the max to the constraint (to be minimized)
        const VariableOffset v_to_tie = c.type==Constraint::VEC_MAX_EQUAL ? c.vec.back() : c.vec.front();
        Variables.emplace_back((c.type==Constraint::VEC_MAX_EQUAL ? "MAX for " : "MIN for ") + Variables[v_to_tie.var].name, Variables[v_to_tie.var].block);
        const int col = Variables.back().mapped_to.var = glp_add_cols(lp, 1);
        Variables.back().mapped_to.off = 0;
        //lower bound is zero
        glp_set_col_bnds(lp, col, GLP_LO, 0, 0);
        //aim to minimize it.
        glp_set_obj_coef(lp, col, c.block->level+1);  //the deeper in the hierarchy, the more we wrap around
        const double sign = c.type==Constraint::VEC_MAX_EQUAL ? 1 : -1;
        //add constraint
        //   - MIN case: [col] = [v_to_tie] - [c.s1.v1]
        //   - MAX case: [col] = [c.s1.v1] - [v_to_tie]
        const int index[4] = {0, col, int(Variables[v_to_tie.var].mapped_to.var), int(Variables[c.s1.v1].mapped_to.var)};
        const double value[4] = {0, 1, sign, -sign};
        glp_set_mat_row(lp, row, 3, index, value);
        const double off = v_to_tie.offset + Variables[v_to_tie.var].mapped_to.off - Variables[c.s1.v1].mapped_to.off;
        glp_set_row_bnds(lp, row, GLP_FX, -sign*off, -sign*off);
    }
}

/** Sort Constraints and simplify the set keeping it equivalent.
 * After sorting, we aim to reduce the number of variables by
 * exploiting v1=v2 (must) rules. This results in a mapping
 * between internal and external variables.
 * Detect if a higher prio constraint is stronger
 * than a lower prio and mark the latter as invalid.
 * We also store the latter in the former, so if we mark the former
 * as invalid, we can re-validate the latter.
 * Constraint A==B is stronger than A<=B. */
bool GLPKSolver::Prune() {
    no_removed_constraints = 0;
    //sort in *decreasing* order of priority.
    std::stable_sort(Constraints.rbegin(), Constraints.rend());
    //zero separated groups of variables that are equivalent and an offset compared to the
    //last variable in the equivalence group. E.g.,
    // if we have (0, 0.0) (42, 12.5), (23, 0.0) (0, 0.0) then the external
    // variable 42 shall have the same value as the external variable 23 plus 12.5.
    std::vector<VariableOffset> eq;
    for (unsigned u = 1; u<Constraints.size(); u++)
        if (Constraints[u].prio!=EAlignPrio::Must) break;
        else if (Constraints[u].type==Constraint::EType::EQUAL
                 && Constraints[u].s1.is_simple_variable()
                 && Constraints[u].s2.is_simple_variable()) {
            double constraint_offset = Constraints[u].s2.offset - Constraints[u].s1.offset;
            // OK, this is a must constraint that equates two variable (maybe with an offset),
            // we can remove one.
            //First check if we have already find equivalences for both of them
            auto i1 = std::ranges::find(eq, Constraints[u].s1.v1, &VariableOffset::var);
            auto i2 = std::ranges::find(eq, Constraints[u].s2.v1, &VariableOffset::var);
            if (i1!=eq.end() && i2!=eq.end()) {
                //both have been already found.
                if (i2<i1) {
                    std::swap(i1, i2);
                    constraint_offset *= -1;
                }
                //If they are in the same group already, eq is fully up-to-date
                auto i1_end = std::ranges::find(i1, i2, 0, &VariableOffset::var); //position of zero after i1
                if (i1_end==i2) {
                    //They are already in the equivalence group, check that their offset is the
                    //same as in this constraint
                    const double equivalence_offset = i2->offset - i1->offset;
                    if (fabs(equivalence_offset - constraint_offset) > 0.001)
                        return true;
                } else {
                    const double offset_delta = i2->offset - i1->offset + constraint_offset;
                    //OK, there is a zero in between i1 and i2 - the two variables were in a
                    //different group. Now merge these groups.
                    auto i1_begin = std::ranges::find(std::make_reverse_iterator(i1), eq.rend(), 0, &VariableOffset::var).base();
                    //now the array looks like this
                    // ..... 0 v1 v2 v3 v4 0 ... 0 vA vB vC vD 0 ...
                    //         ^     ^     ^          ^
                    //         |     |     |          |
                    //  i1_begin     i1   i1_end     i2
                    //We need to move [i1_begin, i1_end) to just before i2 to merge the two groups
                    //"This is a rotate" - Sean Penn
                    auto returned = std::rotate(i1_begin, i1_end, i2);
                    //now the array looks like this
                    // ..... 0 0 ... 0 vA v1 v2 v3 v4 vB vC vD 0 ...
                    //         ^          ^           ^
                    //         |          |           |
                    //  i1_begin      returned       i2
                    //Now adjust the offset in [returned, i2)
                    std::for_each(returned, i2, [offset_delta](VariableOffset &vo) { vo.offset += offset_delta; });
                    eq.erase(i1_begin); //erase redundant zero
                }
            } else if (i1!=eq.end()) {  //s1.v1 is already part of an equivalence group
                eq.emplace(i1, Constraints[u].s2.v1, i1->offset - constraint_offset); //add s2.v1 to it
            } else if (i2!=eq.end()) {
                eq.emplace(i2, Constraints[u].s1.v1, i2->offset + constraint_offset);
            } else {
                //None of them were part of an equivalence group, create a new one
                eq.emplace_back(0, 0.0);
                eq.emplace_back(Constraints[u].s1.v1, constraint_offset);
                eq.emplace_back(Constraints[u].s2.v1, 0.0);
            }
            //Now delete the constraint
            Constraints.erase(Constraints.begin()+u);
            u--; //never wraps around as we start from 1.
            no_removed_constraints++;
        }
    if (eq.size()) { //We found some variables
        //create a mapping from the public variable numbers to the internal ones
        std::vector<bool> mapped(Variables.size(), false);
        //Now walk the equivalence groups, map them to the first element and remove from Variables
        //Walk backwards so that we
        for (unsigned first = 0; VariableOffset &v : std::ranges::reverse_view{eq})
            if (v.var==0)
                first = 0;
            else if (first==0) {
                first = v.var;
                _ASSERT(v.offset==0);
            } else { //map v to first
                mapped[v.var] = true;
                Variables[v.var].mapped_to = {first, v.offset};
                //we deleted variable v, decrease the id of the ones after
                for (unsigned u = v.var+1; u<Variables.size(); u++)
                    if (!mapped[u]) Variables[u].mapped_to.var--;
            }
        for (unsigned u = 1; u<Variables.size(); u++)
            if (mapped[u]) {
                _ASSERT(!mapped[Variables[u].mapped_to.var]);
                _ASSERT(Variables[Variables[u].mapped_to.var].mapped_to.off==0);
                Variables[u].mapped_to.var = Variables[Variables[u].mapped_to.var].mapped_to.var;
            }
        no_GLPK_variables = int(std::ranges::count(mapped, false)) - 1; //index #0 is not a needed variable, hence -1
    } else {
        for (unsigned u = 1; u<Variables.size(); u++)
            Variables[u].mapped_to = {u, 0};
        no_GLPK_variables = unsigned(Variables.size()) - 1;
    }

    //Now purge constraints that became automatically true
    bool problem = false;
    std::erase_if(Constraints, [this, &problem](Constraint &c) {
        if (c.IsVec()) {
            std::erase_if(c.vec, [this, &problem, v=Variables[c.s1.v1].mapped_to, is_min=c.type==Constraint::VEC_MIN_EQUAL](const VariableOffset &vo) {
                if (Variables[vo.var].mapped_to.var != v.var) return false;
                //Ok, v1 and and the vector element map to the same GLPK variable
                if (    ( is_min && v.off > vo.offset+Variables[vo.var].mapped_to.off)   //for a min constraint the left hand side must have a smaller combined offset (to the same GLPK variable)
                     || (!is_min && v.off < vo.offset+Variables[vo.var].mapped_to.off) ) //for a max constraint the left hand side must have a larger combined offset
                    problem = true;
                return true;
            });
            if (!problem) {
                //sort the variables by the GLPK variable we map to.
                //make sure that for VEC_MIN_EQUAL we have the one with the smallest offset first among the
                //variables that map to the same GLPK variable, and for VEC_MAX_EQUAL the one with the largest offset.
                if (c.type==Constraint::VEC_MIN_EQUAL)
                    std::ranges::sort(c.vec, {}, [this](const VariableOffset &vo)
                                      { auto &m = Variables[vo.var].mapped_to; return std::pair(m.var,  m.off + vo.offset); });
                else
                    std::ranges::sort(c.vec, {}, [this](const VariableOffset &vo)
                                      { auto &m = Variables[vo.var].mapped_to; return std::pair(m.var, -m.off - vo.offset); });
                //now remove duplicates
                auto [from, last] = std::ranges::unique(c.vec, {}, [this](const VariableOffset &vo) { return Variables[vo.var].mapped_to.var; });
                c.vec.erase(from, last);
            }
            return c.vec.empty();
        } else {
            //Ensure that we have no variable on the two sides that map to the same GLPK variable
            //(GLPK panics if it has a row with the same variable several times)
            //1. Handle when the two variables on the same side map to the same GLPK variable
            for (auto *s :{&c.s1, &c.s2})
                if (s->w1 && s->w2 && Variables[s->v1].mapped_to.var == Variables[s->v2].mapped_to.var) {
                    //Ok, now the two external variables s->v1 and s->v2 map to the same GLPK variable,
                    //but they may have diffeent offset. If they both map to V, with O1 and O2 offsets, then
                    //this side looks like
                    //    w1*v1 + w2*v1
                    //  = w1*(V + O1) + w2*(V + O2)
                    //  = (w1+w2)*V + w1*O1 + w2*O2
                    //  = (w1+w2)*(V + O1) - (w1+w2)*O1 + w1*O1 + w2*O2
                    //  = (w1+w2)*(V + O1) - w1*O1 - w2*O1 + w1*O1 + w2*O2
                    //  = (w1+w2)*(V + O1) + w2*(O2-O1)
                    //  = (w1+w2)*v1 + w2*(O2-O1)
                    s->offset += s->w2*(Variables[s->v2].mapped_to.off - Variables[s->v1].mapped_to.off);
                    s->w1 += s->w2;
                    s->w2 = 0;
                }
            //2. Handle when the two variables on the different sides map to the same GLPK variable
            for (auto [vA, wA] :{std::pair{c.s1.v1, &c.s1.w1}, std::pair{c.s1.v2, &c.s1.w2}})
                for (auto [vB, wB] :{std::pair{c.s2.v1, &c.s2.w1}, std::pair{c.s2.v2, &c.s2.w2}})
                    if (wA && wB && Variables[vA].mapped_to.var == Variables[vB].mapped_to.var) {
                        //Ok, now the two external variables vA and vB on *different* sides of the equation
                        //map to the same GLPK variable, but they may have different offset.
                        //If they both map to V, with oA and oB offsets, then the eqn (assuming inequality)
                        //looks like (assuming for the ease of explanation that each side has only one variable)
                        //    wA*vA >= wB*vA
                        //  = wA*(V + oA) >= wB*(V + oB)
                        //  = (wA-wB)*V >= -wA*oA + wB*oB
                        //  = (wA-wB)*(V + oA) - (wA-wB)*oA >= -wA*oA + wB*oB
                        //  = (wA-wB)*(V + oA) - wA*oA + wB*oA >= -wA*oA + wB*oB
                        //  = (wA-wB)*(V + oA) >= wB*(oB-oA)
                        //  = (wA-wB)*vA >= wB*(oB-oA)
                        // First update the offset
                        const double offset_change = *wB*(Variables[vB].mapped_to.off - Variables[vA].mapped_to.off);
                        c.s2.offset += offset_change;
                        *wA -= *wB;
                        *wB = 0;
                    }
            //2b Now sanitize the sides: if we only have one variable, that shoud be v1
            for (auto *s :{&c.s1, &c.s2})
                if (s->w1==0 && s->w2!=0)
                    s->v1 = s->v2, s->w1 = s->w2, s->w2 = 0;
            //Now it may be that all the variables of the two sides map to the same GLPK variable
            //In that case we moved all the weight of this variable to s1 and s2 became empty
            if (c.s2.w1) return false;
            //In this case we have to delete this constraint. Check if we have a problem
            switch (c.type) {
            case Constraint::EQUAL: problem |= c.s1.offset!=c.s2.offset; break;
            case Constraint::INEQUAL: problem |= c.s1.offset<c.s2.offset; break;
            case Constraint::DIFF_MIN: break;
            case Constraint::VEC_MIN_EQUAL: //vectors handled above
            case Constraint::VEC_MAX_EQUAL:
            default: _ASSERT(0);
            }
            if (problem)
                return true;
            return true;
        }
    });
    if (problem) return true;
    for (unsigned u = 1; u<Constraints.size(); u++)
        //Search backwards so that chains of ever weaker links can form
        for (unsigned v = u; v; v--) {
            if (Constraints[v-1].weaker_than_me!=-1) continue;
            switch (Constraints[v-1].Relation(Constraints[u])) {
            case Constraint::ERelation::Stronger:
                //If the higher prio constraint is stronger, we prune the
                //weaker one, but mark it as something to re-validate,
                //if the stronger one cannot be satisfied.
                Constraints[v-1].weaker_than_me = u;
                FALLTHROUGH;
            case Constraint::ERelation::Equivalent:
                //If we are equivalent, we mark the lower prio as pruned, but
                //as something never to re-validate, since if the higher
                //prio cannot be satisfied, neither can the lower prio.
                Constraints[u].valid = Constraint::EValidity::Pruned;
                FALLTHROUGH;
            default:
                break;
			case Constraint::ERelation::Weaker:
				//If the former is weaker, but they are both 'Must'
				//then their order does not really count, so we can still prune
				//the weaker
                if (Constraints[u].prio==EAlignPrio::Must && Constraints[v-1].prio==EAlignPrio::Must)
                    Constraints[v-1].valid = Constraint::EValidity::Pruned;
            }
        }
    return false;
}


void GLPKSolver::SetConstrainsTill(unsigned end)
{
    //Previously we had a system where as we iterate through the constraints
    //we kept rows we still want to use. This did not work for some mysterious
    //reason, so now we dump all rows and re-add all of them. Inefficient by I
    //did not have the time to debug why.
    while (glp_get_num_rows(lp)) {
        const int till_del[] = {0, glp_get_num_rows(lp)};
        glp_del_rows(lp, 1, till_del);
    }
    //We only keep the variables added by the Blocks, not the ones added by
    //GLPKSolver::AddConstraint() for min and max constraints.
    while (glp_get_num_cols(lp)>int(no_GLPK_variables)) {
        const int till_del[] = {0, glp_get_num_cols(lp)};
        glp_del_cols(lp, 1, till_del);
        Variables.pop_back();
    }
    _ASSERT(end<=Constraints.size());
    for (unsigned u = 0; u<end; u++)
        AddConstraint(Constraints[u]);  //handles invalid constraints & adds extra variables needed
    glp_sort_matrix(lp);
    added_end = end;
}

//Solve what rows we have in glpk now
//True if not solvable
//If force is true, we solve even if we may have solved it before
//This is used to gather auxiliary info in  InvalidateContstraint()
bool GLPKSolver::SolveOne(int recurse)
{
    no_passes++;
    glp_std_basis(lp);
    glp_smcp param;
    glp_init_smcp(&param);
    //param.presolve = GLP_ON;
    param.msg_lev = GLP_MSG_OFF; //dont send anything to the terminal.
    int res = glp_simplex(lp, &param);
    if (res)
        return true;
    res = glp_get_status(lp);
    if (res!=GLP_OPT && res!=GLP_FEAS)
        return true;
    //Now test if all MIN or MAX constraints were fulfilled.
    //That is, at least one variable in the vector is equal to the variable
    //that supposed to be MIN or MAX.
    int added_row = 0;
    int size_before = glp_get_num_rows(lp);
    for (unsigned u=0; u<added_end; u++) {
        auto &c = Constraints[u];
        if (c.valid==Constraint::EValidity::Valid && c.IsVec() && c.vec.size()>1) {
			_ASSERT(c.s1.offset==0 && c.s1.w2==0 && c.s1.w1==1);
            const double val_to = GetVal(c.s1.v1);
            const auto minmax = std::minmax_element(c.vec.begin(), c.vec.end(),
                                     [this](auto a, auto b) {return GetVal(a)<GetVal(b); });
            const VariableOffset &varoff_minormax = c.type==Constraint::VEC_MIN_EQUAL ? *minmax.first : *minmax.second;
			const double val_minormax = GetVal(varoff_minormax);
            if (fabs(val_to-val_minormax)<0.5) continue;
            //OK. problem
            //One of the MIN or MAX constraints (which we cannot express as linear rows)
            //is not fulfilled. So we select the variable closest to the desired MIN or MAX value
            //and add an equality constraint. Hopefully by and large the chart is laid out OK,
            //so this is indeed the one supposed to be closest.
            //Then solve again and see what happens.

            //Add an extra constraint
            int row = glp_add_rows(lp, 1);
            int index[3] = {0, int(Variables[c.s1.v1].mapped_to.var), int(Variables[varoff_minormax.var].mapped_to.var)};
            double values[3] = {0, 1, -1};
            glp_set_mat_row(lp, row, 2, index, values);
            const double off = varoff_minormax.offset + Variables[varoff_minormax.var].mapped_to.off - Variables[c.s1.v1].mapped_to.off;
            glp_set_row_bnds(lp, row, GLP_FX, off, off);
            added_row++;
        }
    }
    if (added_row) {
        bool ret = recurse==0  || SolveOne(recurse-1);
        //remove extra rows
        while (glp_get_num_rows(lp)>size_before) {
            const int to_del[] = {0, glp_get_num_rows(lp)};
            glp_del_rows(lp, 1, to_del);
        }
        if (ret) return true;
    }
    //read out the solution
    for (unsigned u = 1; u<Variables.size(); u++)
        Variables[u].value = GetVal(u);
    return false;
}

/** Prepare a solution statisfying all constraints marked with
 * Must priority. We assume Constraints are sorted by decreasing prio.
 * @returns Returns the index of the first non-Must prio Constraint.
 *          Or zero if no solution can be found.*/
unsigned GLPKSolver::SolveMust()
{
    const unsigned first_non_mandatory =
        unsigned(std::find_if_not(Constraints.begin(), Constraints.end(),
                         [](auto &c) {return c.prio==EAlignPrio::Must; }) -
                 Constraints.begin());
    SetConstrainsTill(first_non_mandatory);
    if (SolveOne(passes_must))
        return 0;
    return first_non_mandatory;
}


/** See which of the higher prio rules actually conflict with this one.
 * @param [in] constraint A constraint we have just marked invalid.
 * We check all non-mandatory, valid rules before 'constraint'
 * if disabling them would make the current rule and add them
 * to constraint->conflicts. */
void GLPKSolver::InvalidateContstraint(unsigned constraint)
{
    //Check if removing a rule below 'constraint' would make us solvable.
    if (Constraints[constraint].prio >= prio_for_alt) {
        auto save = no_passes;
        for (unsigned u = 0; u<constraint; u++)
            if (Constraints[u].valid == Constraint::EValidity::Valid &&
                Constraints[u].prio!=EAlignPrio::Must) {
                SetConstrainsTill(u);
                Constraints[u].valid = Constraint::EValidity::Conflicted;
                SetConstrainsTill(constraint+1);
                //If we can solve it with 'conflict' enabled and 'u' disabled,
                //we consider 'u' conflicting with 'constraint'
                if (!SolveOne(1))
                    Constraints[constraint].conflicts.push_back(u);
                //re-enable 'u'
                SetConstrainsTill(u);
                Constraints[u].valid = Constraint::EValidity::Valid;
            }
        no_passes_4xtra += no_passes - save;
        no_passes = save;
    }
    SetConstrainsTill(constraint); //manually remove the rows we will not re-add due to invalid Constraint
    Constraints[constraint].valid = Constraint::EValidity::Conflicted;
    //If we have shadowed a weaker rule, activate that
    if (Constraints[constraint].weaker_than_me!=-1) {
        //Should be later than us
        _ASSERT(Constraints[constraint].weaker_than_me > int(constraint));
        Constraints[Constraints[constraint].weaker_than_me].valid = Constraint::EValidity::Valid;
    }
}

/** Solve all constraints using a linear search algorithm.
 * This is beneficial if the solution has many conflicts.
 * We use this, because the case when we have few conflicts
 * we need to do less work in general - and we need the speed
 * when we have many conflicts and need to do more work.
 * Returns true on failure.*/
bool GLPKSolver::Solve() {
    unsigned u = SolveMust();
    if (u==0) return true;
    if (u==Constraints.size()) return false;
    const double unit = double(total_units)/(Constraints.size()-u);
    const size_t started_from = u;
    size_t reported_until = 0;
    size_t step = std::ranges::count(Constraints.begin()+u, Constraints.end(), Constraint::EValidity::Valid, &Constraint::valid); //Try doing all of them at once.
    while (u<Constraints.size()) {
        //consider 'step' number of non-pruned constraints
        unsigned till = u;
        for (size_t s = step; till<Constraints.size() && s; till++)
            if (Constraints[till].valid==Constraint::EValidity::Valid)
                s--;
        SetConstrainsTill(till);
        if (SolveOne(passes_other)) { //we failed
            if (step==1) {
                SetConstrainsTill(u); //remove the constraints at and after 'u'
                _ASSERT(Constraints[till-1].valid==Constraint::EValidity::Valid);
                InvalidateContstraint(till-1); //there may be PRUNED constraints between u and till, invalidate the sole valid one (sole, because step==1)
                u = till;
            } else
                step = step/2;
        } else {
            step += 1;
            u = till;
        }
        if (const unsigned report_till = unsigned((u-started_from)*unit)
            ; report_till>reported_until) {
            Progress.DoneBulk(ESections::LAYOUT_BLOCK, report_till-reported_until);
            reported_until = report_till;
        }
    }
    return false;
}

/** Emit error messages for conflicting constraints. */
LayoutStat GLPKSolver::EmitErrors(MscError &Error) {
    for (auto &c : Constraints)
        if (c.valid == Constraint::EValidity::Conflicted) {
            FileLineCol l = c.GenerateError(Error, prio_for_alt);
            if (!l.IsInvalid())
                for (unsigned u : c.conflicts)
                    Constraints[u].GenerateErrorForAlternative(Error, l);
        }

    LayoutStat ret;
    ret.no_constraints = Constraints.size();
    ret.no_must_constraints = std::find_if_not(Constraints.begin(), Constraints.end(),
                                               [](auto &c) {return c.prio>=EAlignPrio::Must; })
                              - Constraints.begin();
    ret.no_removed_constraints = no_removed_constraints;
    ret.no_pruned_constraints =
        std::count_if(Constraints.begin(), Constraints.end(), [](const auto &c) {return c.valid==Constraint::EValidity::Pruned; });
    ret.no_rejected_constraints =
        std::count_if(Constraints.begin(), Constraints.end(), [](const auto &c) {return c.valid==Constraint::EValidity::Conflicted; });
    ret.no_variables = Variables.size();
    ret.no_removed_variables = Variables.size() - no_GLPK_variables;
    ret.no_passes = no_passes;
    return ret;
}


/** After the variables are calculated, copy them from solver.Variables recursively.
 * This sets, outer_line and inner_line (if we have label or content) and label_block
 * (if we have label).*/
void BlockBlock::LayoutFromVariables(GLPKSolver &solver, unsigned from, unsigned to)
{
    const bool has_visible_label = 0 < available_for_label.x && 0 < available_for_label.y;
    for (unsigned xy = from; xy<=to; ++xy) {
        outer_line[xy] =
              {solver.GetVarValue(index_out_min[xy]), solver.GetVarValue(index_out_max[xy])};
        if (has_visible_label || has_visible_content)
            inner_line[xy] =
                  {solver.GetVarValue(index_in_min[xy]), solver.GetVarValue(index_in_max[xy])};
        //if we have no content, we leave inner line unfilled.
        if (has_visible_label) {
            Range &range = label_block[xy];
            if (has_visible_content) {
                range =
                    {solver.GetVarValue(index_label_min[xy]),
                     solver.GetVarValue(index_label_max[xy])};
            } else {
                const double size = available_for_label[xy];
                const auto pos = style.label_pos.value_or(EDirection::Above);
                if (pos == EDirection::Invalid) { //invalid is used as a proxy for center
                    range = {(solver.GetVarValue(index_in_min[xy]) + solver.GetVarValue(index_in_max[xy]))/2 - size/2,
                             (solver.GetVarValue(index_in_min[xy]) + solver.GetVarValue(index_in_max[xy]))/2 + size/2};
                } else if (IsVertical(pos) == (xy==1)) {
                    if (IsMin(pos))
                        range = {solver.GetVarValue(index_in_min[xy]),
                                 solver.GetVarValue(index_in_min[xy]) + size};
                    else
                        range = {solver.GetVarValue(index_in_max[xy]) - size,
                                 solver.GetVarValue(index_in_max[xy])};
                } else {
                    const double la = style.label_align.value_or(0.5);
                    const Range in = {solver.GetVarValue(index_in_min[xy]), solver.GetVarValue(index_in_max[xy])};
                    range.from = in.from + la*(in.Spans()-size);
                    range.till = range.from + size;
                }
            }
        }
    }
    for (auto& c : content)
        c->LayoutFromVariables(solver, from, to);
}

/** Create a layout for the Block and its contents.
 * Can only be used if none of the variables of the block (or its descendants) have
 * any constraint outside this block.
 * At the end the block will be laid out to (0,0) and will be marked as ready.
 * Any next call to AddVariableIndices(), AddConstraints() and LayoutFromVariables()
 * will just add the outline of this box with the pre-calculated size and will only
 * move the block (and its content) to the new position.
 * Parameters 'from' and 'to' can only be 0 or 1 and from<=to must hold.
 * @param canvas The canvas to lay out on.
 * @param [in] from If zero, we lay out the x coordinates (maybe in addition to
 *                  the y coordinates).
 * @param [in] to If one, we lay out the y coordinates (maybe in addition to
 *                  the x coordinates).
 * @param [in] progress_units We shall report this many progress units when we are done.
 * @returns true on error, false on success.*/
bool BlockBlock::LayoutWithGLPK(Canvas &canvas, unsigned from, unsigned to, size_t progress_units) {
    //GLPKSolver must be a singleton as GLPK is not reentrant.
    GLPKSolver solver(chart.conflict_report, progress_units, chart.Progress);

    AddVariableIndices(solver, from, to);
    unsigned dummy[2] = {0, 0};
    const InternalAlignment *dummy2[2] = {nullptr, nullptr};
    AddConstraints(canvas, solver, from, to, dummy, dummy2);

    //Prune the constraints & set up GLPK. Fail if we got a conflict among the Must constraints
    if (solver.Initialize()) return true;

    //Bound the left and top edge of this block to be at least zero
    if (from==0) solver.ForceVariableToZero(index_out_min[0]);
    if (to==1)   solver.ForceVariableToZero(index_out_min[1]);

    if (solver.Solve()) return true;

    //Copy the result to the blocks
    LayoutFromVariables(solver, from, to);
    //Print errors for constraints we had to invalidate
    chart.layout_stat += solver.EmitErrors(chart.Error);
    return false; //success
}

void BlockBlock::LayoutFinalizeBlocks(Canvas & canvas, ELayoutContentFinalizePass p)
{
    if (!outer_line.IsInvalid() && 0<outer_line.x.Spans() && 0<outer_line.y.Spans() 
             && HasArea(type) && ELayoutContentFinalizePass::BLOCK==p) {
        //Set label scaling if necessary
        if (style.label_mode.value_or(ELabelMode::ENLARGE)==ELabelMode::SCALE) {
            const XY twh = GetLabelSize();
            const double r = std::min(available_for_label.x/twh.x, available_for_label.y/twh.y);
            if (r<1)
                parsed_label.SetScale(r, r);
            //TODO: align label within label_box
        } else if (style.label_mode.value_or(ELabelMode::ENLARGE)==ELabelMode::SCALE_2D) {
            const XY twh = GetLabelSize();
            parsed_label.SetScale(std::min(1., available_for_label.x/twh.x),
                                  std::min(1., available_for_label.y/twh.y));
        }

        if (shape>=0 && type==EBlockType::Shape)
            area = chart.Shapes.Cover(shape, outer_line);
        else if (type==EBlockType::Text)
            area = LabelCover();
         else if (style.f_line && style.line.IsComplete())
            area = style.line.CreateRectangle_OuterEdge(style.line.width ?
                                                        outer_line.CreateExpand(-style.line.LineWidth()/2) :
                                                        outer_line);
        else
            area = outer_line;
        area.arc = this;
    }
    for (auto& c : content)
        c->LayoutFinalizeBlocks(canvas, p);
}

/** If a port is used with the compass point 'perp', resolve it to
 * a specific direction. This can be called only after layout.
 * @return A number between [0..360), where north is 0 and increase is clockwise.*/
double BlockBlock::GetPerpendicularDir(const EdgePos &x, const EdgePos &y) const
{
    const Range xr = {
        outer_line.x.from - (x.margin ? style.min_margin[0].value_or(0) : 0),
        outer_line.x.till + (x.margin ? style.max_margin[0].value_or(0) : 0)
    };
    const Range yr = {
        outer_line.y.from - (y.margin ? style.min_margin[1].value_or(0) : 0),
        outer_line.y.till + (y.margin ? style.max_margin[1].value_or(0) : 0)
    };
    const XY port = {
        x.pos*xr.till + (1-x.pos)*xr.from + x.offset,
        y.pos*yr.till + (1-y.pos)*yr.from + y.offset
    };
    //Now find the point closest to the port on the contour
    XY point, t_fw, t_bw;
    if (CONTOUR_INFINITY == area.DistanceWithTangents(port, point, t_fw, t_bw))
        return -1;
    const XY vector = t_bw-point;
	//Atan of the forward tangent is zero, when the perpendicular direction is north (90 degree clockwise rotation)
	const double degree = atan2(vector.y, vector.x)*(180./M_PI);
    return fmod_negative_safe(degree, 360.);
}

void BlockBlock::PostPosProcess(Canvas &canvas, Chart *ch)
{
    BlockElement::PostPosProcess(canvas, ch);
    if (outer_line.x.Spans() <= 0 && outer_line.y.Spans() <= 0)
        return;
    Block b = outer_line;
    b.x.from -= style.min_margin[0].value_or(0);
    b.x.till += style.max_margin[0].value_or(0);
    b.y.from -= style.min_margin[1].value_or(0);
    b.y.till += style.max_margin[1].value_or(0);
    b.x.till += style.shadow.offset.value_or(0);
    b.y.till += style.shadow.offset.value_or(0);
    chart.IncreaseTotal(b);
    if (track)
        chart.AllCovers += area;
}



void BlockBlock::Draw(Canvas & canvas) const
{
    if (outer_line.x.Spans() <= 0 && outer_line.y.Spans() <= 0)
        return;
    const double lw = style.f_line ? style.line.LineWidth() : 0;
    if (canvas.does_graphics()) {
        if (shape<0) {
            const Block b = outer_line.CreateExpand(-lw/2);
            if (style.f_shadow)
                canvas.Shadow(b, style.line, style.shadow);
            if (style.f_fill) {
                if (style.f_line)
                    canvas.Fill(style.line.CreateRectangle_ForFill(b), style.fill);
                else
                    canvas.Fill(b, style.fill);
            }
            if (style.f_line)
                canvas.Line(b, style.line);
        } else {
            chart.Shapes.Draw(canvas, shape, outer_line, style.line, style.fill, style.shadow);
        }
        DrawLabel(canvas);
    } else {
        bool draw_label = !delay_label_drawing && style.f_text && !parsed_label.IsEmpty();
        if (shape<0) {
            if (draw_label 
                    && style.label_orient.value_or(EOrientation::Normal)== EOrientation::Normal
                    && label_block.Centroid().DistanceSqr(outer_line.Centroid())<1) {
                canvas.Add(GSBox(outer_line, style.f_line ? style.line : LineAttr::None(),
                                 style.f_fill ? style.fill : FillAttr::None(),
                                 style.f_shadow ? style.shadow : ShadowAttr::None(),
                                 parsed_label, ETextAlign::Min));
                draw_label = false;
            } else
                canvas.Add(GSBox(outer_line, style.f_line ? style.line : LineAttr::None(),
                                 style.f_fill ? style.fill : FillAttr::None(),
                                 style.f_shadow ? style.shadow : ShadowAttr::None()));
        } else {
            canvas.AddMore(GSMscShape(chart.Shapes, shape, outer_line,
                                      style.f_line ? style.line : LineAttr::None(),
                                      style.f_fill ? style.fill : FillAttr::None(),
                                      style.f_shadow ? style.shadow : ShadowAttr::None(),
                                      draw_label ? parsed_label : Label{}, true));
            draw_label = false;
        }
        if (draw_label) DrawLabel(canvas);
    }
    if (chart.draw_margins && this!=&chart.Blocks) {
        Block bb(outer_line.x.from-(style.min_margin[0].value_or(0)),
                 outer_line.x.till+(style.max_margin[0].value_or(0)),
                 outer_line.y.from-(style.min_margin[1].value_or(0)),
                 outer_line.y.till+(style.max_margin[1].value_or(0)));
        LineAttr line(ELineType::DOTTED, ColorType::black(), 0.5);
        if (canvas.does_graphics())
            canvas.Line(bb, line);
        else
            canvas.Add(GSBox(bb, line));
    }
}

/*************************************************************************
* MultiBlock
*************************************************************************/

MultiBlock::MultiBlock(int num, gsl::owner<BlockBlock *> b, const FileLineColRange &l) :
    BlockBlock(b->chart, EBlockType::Row, b->content_type, -1, l, b->name_original),
    original_block(b), count(num)
{
    //copy relevant things that should apply to the whole multiblock series
    valid = b->valid;
    //numberingStyle captured well in BlockElement::BlockElement
    //wildcard repl is not needed
}


/** Test what to add an attribute applied after creation to.
* - Returns 0 if this attribute shall be added to the MultiBlock itself (alignment, margin)
* - Returns 1 if this attribute shall be added to the front child (shadown and most other)
* - Returns 2 if this attribute shall be added to all children (line and fill) */
int MultiBlock::CategorizeAttribute(const Attribute& a) {
    //Alignment, margin attributes shall be added to the main block
    if (AlignmentAttr::AttributeNameDir(a.name) ||
            CaseInsensitiveBeginsWith(a.name, "margin") ||
            CaseInsensitiveEqual(a.name, "xpos") ||
            CaseInsensitiveEqual(a.name, "ypos"))
        return 0;
    else if (CaseInsensitiveBeginsWith(a.name, "line") ||
             CaseInsensitiveBeginsWith(a.name, "fill"))
        //Line and fill attributes to all the contents
        return 2;
    else
        //The rest only to the first. This includes shadow
        return 1;
}
AttributeList* MultiBlock::CopyAttributes4Children(const AttributeList* al, bool front) {
    if (!al) return nullptr;
    std::unique_ptr<AttributeList> ret = std::make_unique<AttributeList>();
    for (auto& a : *al)
        if (int cat = CategorizeAttribute(*a); a == 0) continue;
        else if (cat == 1 && !front) continue;
        else ret->Append(std::make_unique<Attribute>(*a));
    if (ret->empty()) ret.reset();
    return ret.release();
}

void MultiBlock::ApplyFurtherAttributes(const AttributeList * al)
{
	if (al)
		for (auto &a : *al)
            if (a)
                switch (CategorizeAttribute(*a)) {
			    //Alignment, margin attributes shall be added to the main block
                case 0:
				    if (!AddAttribute(*a))
					    chart.Error.Error(*a, false, "Unrecognized or inapplicable attribute. Ignoring it.");
                    break;
                case 2:
				    //Line and fill attributes to all the contents
				    for (auto &pPart : content)
					    if (!pPart->AddAttribute(*a)) {
						    chart.Error.Error(*a, false, "Unrecognized or inapplicable attribute. Ignoring it.");
						    break;
					    }
                    break;
                case 1:
				    //The rest only to the first. This includes shadow
				    if (!content.back()->AddAttribute(*a))
                        chart.Error.Error(*a, false, "Unrecognized or inapplicable attribute. Ignoring it.");
                    break;
                }
}


void MultiBlock::SetContent(std::variant<nullptr_t, BlockInstrList*, StringWithPos> c)
{
    //Here we need to create the copies
    //save the context's prefix and use our full name instead
    Parent save_parent = chart.MyCurrentContext().parent;
    chart.MyCurrentContext().parent = this;
    std::unique_ptr<BlockInstrList> my_c = std::make_unique<BlockInstrList>();
    BlockInstruction *first_content = nullptr;
    const XY offset = {style.multi_offset[0].value_or(5), style.multi_offset[1].value_or(-5)};

    if (valid) RegisterNames(); //this computes 'name_gui_collapse'
    original_block->name_gui_collapse = name_gui_collapse;
    original_block->UpdateCollapsedFromGUI();

    for (unsigned u = 0; u<count; u++) {
        auto b = original_block->shape<0 ?
            std::make_unique<BlockBlock>(chart, original_block->type, original_block->file_pos,
                                         name_original.length()==0 ? "" :
                                         u==0 ? "front" : unsigned(u) == count-1 ? "back" : std::to_string(u+1)) :
            std::make_unique<BlockBlock>(chart, original_block->shape, original_block->file_pos,
                                         name_original.length()==0 ? "" :
                                         u==0 ? "front" : unsigned(u) == count-1 ? "back" : std::to_string(u+1));
        b->style = original_block->style;
        b->orig_collapsed = original_block->orig_collapsed;
        b->collapsed = original_block->collapsed;
		//Prevent us from generating a track rectangle.
        b->track = false;
		b->SetContent(u==0 ? c : nullptr);
		if (u==0) {
            if (b->content.size())
				first_content = b->content.front().get();
			b->wildcard_repl = name_original;  //The first element shall use the name of the 'multi' and not "front"
			//Copy relevant attributes to us.
			for (unsigned xy = 0; xy<2; xy++) {
				style.min_margin[xy] = b->style.min_margin[xy];
				style.max_margin[xy] = b->style.max_margin[xy];
			}
		} else {
			b->style = my_c->back()->style; //copy the first style
		}
		//adjust the alignment of the multi blocks and other stuff
		b->AdjustMultiAttributes(u, offset);
		original_block->file_pos.start.AdvanceCol(1); //ensure that they are on different positions so that they get a unique name
        my_c->push_front(std::move(b));
    }
    //restore the context prefix
    chart.MyCurrentContext().parent = std::move(save_parent);
    //Arrange our alignment
    //('style' holds alignment modifiers, 'original_block->style' holds explicit alignment attrs)
    //(content alignment attrs are not present yet, they will be added in BlockBlock::SetContent()
    //below).
    style.alignment.MergeByDimension(original_block->style.alignment);
    //kill the original block, so that it doesnt get drawn
    //remove and reinsert before content or at end.
	//Use BlockElement::AddToDrawOrder. That one does not re-order the content of 'b'
    for (auto &b : *my_c)
        b->BlockElement::AddToDrawOrder(first_content);
    //OK, now set our own style (names have been registered above)
    BlockBlock::SetContent(my_c.get());
    //zero out our internal margins - the sub-blocks have it
    for (unsigned xy = 0; xy<2; xy++) {
        style.min_imargin[xy] = 0;
        style.max_imargin[xy] = 0;
    }
	style.allow_arrows = true;
    original_block.reset();
}

bool MultiBlock::PostParseProcess(Canvas & canvas, const BlockBlock* hide_by, Numbering & number)
{
	//If we have a shadow attribute, add a join command for shadow to our front
	if (content.back()->style.shadow.offset.value_or(0)>0) {
		StringWithPosList my;
		for (auto &p : content) {
			auto b = dynamic_cast<BlockBlock*>(p.get());
			_ASSERT(b);
			if (b)
				my.emplace_back(b->name_unique, b->file_pos);
		}
		auto j = std::make_unique<JoinCommand>(chart, file_pos, std::move(my));
		//OK, keep only the shadow
		j->style.line.type.reset();
		j->style.fill.color.reset();
		j->style.shadow = content.back()->style.shadow;
		content.push_front(std::move(j));
	}
	const bool ret = BlockBlock::PostParseProcess(canvas, hide_by, number);
    //re-adjust controls. BlockBlock::PostParseProcess() added a control
    //based on our 'collapsed' member, but it should be based on content.back()->collapsed
    controls.clear();
    const BlockBlock* front = dynamic_cast<BlockBlock*>(content.back().get());
    if (front && front->orig_has_visible_content)
        controls.push_back(front->collapsed.value_or(false) ? EGUIControlType::EXPAND : EGUIControlType::COLLAPSE);
    return ret;
}

BlockBlock *MultiBlock::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 [[maybe_unused]]) const
{
    _ASSERT(!orig_collapsed.has_value());
    _ASSERT(!multi_parent);
    MultiBlock *ret = new MultiBlock(*this, file_pos.CreatePushed(l, EInclusionReason::COPY), name, prefix);
    _ASSERT(ret->content.empty());
    ret->RegisterNames();
    ret->UpdateCollapsedFromGUI();
    for (auto& pBlock : content) {
        const bool last_item = &pBlock == &content.back();
        //Synthetize an attribute list to apply to children
        AttributeList* attr_list = CopyAttributes4Children(attr, last_item);
        auto p = pBlock->Clone(file_pos, ret->name_full.size() ? ret->name_full : prefix, attr_list, nullptr, last_item ? ret : nullptr); //deletes attr_list
        p->SetParent(ret);
        p->IncLevel();
        ret->content.Append(p);
    }

    _ASSERT(ret->content.size());
    //We keep the priorities set in 'align' as they are. I expect they are to be 'Explicit' as
    //TranslatePre() generates such alignments.
    if (align) {
        ret->style.alignment.MergeByDimension(*align);
        delete align;
    }
    if (attr) {
        //Delete attributes not for the main block
        attr->remove_if([](const auto& pAttr) { return MultiBlock::CategorizeAttribute(*pAttr); });
        ret->MultiBlock::ApplyFurtherAttributes(attr);
        delete attr;
    }
    auto p = dynamic_cast<BlockBlock*>(ret->content.back().get());
    _ASSERT(p);
	if (p) {
		p->ApplyCloneModifiers(mod);
		p->wildcard_repl = content.back()->wildcard_repl;
	}
    return ret;
}


void MultiBlock::ApplyRunningStyle(bool cloning, bool recursive)
{
    //Of the attributes, alignment ones need to be copied to the MultiBlock,
    //and the rest to its children.
    //We keep the priority of the alignment as specified in the running style.
    //I expect them to be Running_style or Content.
    style.alignment.MergeByDimension(chart.MyCurrentContext().running_style_blocks.read().alignment);
    StyleCoW<BlockStyle> save = chart.MyCurrentContext().running_style_blocks;
    chart.MyCurrentContext().running_style_blocks.write().alignment.Empty();
    if (cloning || recursive)
        for (auto &p:content)
            p->ApplyRunningStyle(cloning, recursive);
    chart.MyCurrentContext().running_style_blocks = std::move(save);
}


void MultiBlock::LayoutFinalizeBlocks(Canvas & canvas, ELayoutContentFinalizePass p)
{
    if (p==ELayoutContentFinalizePass::BLOCK) {
        area.clear();
        area.arc = this;
    }
    for (auto &i : content) {
        i->LayoutFinalizeBlocks(canvas, p);
        if (p==ELayoutContentFinalizePass::BLOCK)
            area += i->GetAreaToDraw();
    }
}
