/*
    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_arrows.cpp The definition for for Arrows of Block Diagrams.
* @ingroup libblock_files */

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


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

using namespace block;

/** Converts 2 number strings (coming from TOK_NUMBER token) with optional
 * percent signs to a label position. We return (0,0) on error and emit
 * appropriate messages.
 * @param [in] chart The chart to emit the errors to.
 * @param [in] num1 The first number string.
 * @param [in] is_perc1 True if the first number had a percentage after.
 * @param [in] l1 The location of the first number (plus percentage).
 * @param [in] num2 The second number string.
 * @param [in] is_perc2 True if the second number had a percentage after.
 * @param [in] l2 The location of the second number (plus percentage).
 * @returns 'first' will be the percentage value and 'second' its offset.*/
std::optional<std::pair<double, double>>
ArrowLabel::ConvertPosition(BlockChart & chart,
                            std::string_view num1, bool is_perc1, const FileLineColRange & l1,
                            std::string_view num2, bool is_perc2, const FileLineColRange & l2)
{
	std::optional<std::pair<double, double>> ret;
    _ASSERT(num1.size());
    double n1=0;
    if (num1.size() && from_chars(num1, n1))
        chart.Error.Error(l1.start, "Expecting a number. Ignoring label.");
    else {
        //if num1 is empty we end up here  with n1=0
        double n2 = 0;
        if (num2.size() && from_chars(num2, n2))
            chart.Error.Error(l2.start, "Expecting a number. Ignoring label.");
        else {
            //if num2 is empty we end up here with n2=0
			ret = { { 0, 0 } };
            (is_perc1 ? ret.value().first : ret.value().second) = n1;
            (is_perc2 ? ret.value().first : ret.value().second) += n2;
            //if (ret.first==0 && ret.second==0)
            //    chart.Error.Error(l1.start, "Expecting a nonzero position. Ignoring label.");
        }
    }
    return ret;
}

ArrowLabel::ArrowLabel(BlockChart &chart, double perc, double off, const FileLineColRange &l) :
    BlockInstruction(chart, l), percent(perc), offset(off)
{
    style = chart.MyCurrentContext().styles["label"].read();
    style.type = EStyleType::ELEMENT;
    style.block_style_type = EBlockStyleType::Unspecified;
    //copy from the arrow running style the text, numbering and label
    if (style.text.IsEmpty())
        style.text = chart.MyCurrentContext().text;
    else {
        StringFormat sf = chart.MyCurrentContext().text;
        sf += style.text;
        style.text = sf;
    }
    ApplyRunningStyle(false, false);
}

bool ArrowLabel::AddAttribute(const Attribute & a)
{
    if (a.Is("extend")) {
        if (a.EnsureNotClear(chart.Error, EStyleType::ELEMENT) &&
            a.CheckType(EAttrType::BOOL, chart.Error))
            is_extension = a.yes;
        return true;
    }
    return BlockInstruction::AddAttribute(a);
}

void ArrowLabel::AttributeNames(Csh &csh)
{
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"extend",
                           "When set to yes, the position of the arrow label is interpreted as before its start (for negative values) or "
                           "after its end (positive values). If set to no (default), positive position values are counted from the start "
                           "towards the end, whereas negative values counted from the end towards the start.",
                           EHintType::ATTR_NAME, true));
    csh.AttributeNamesForStyle("label");
    BlockInstruction::AttributeNames(csh, false);
}

bool ArrowLabel::AttributeValues(std::string_view attr, Csh &csh)
{
    if (CaseInsensitiveEqual(attr, "extend")) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE)+"yes",
                               "The position of the arrow label is interpreted as before its start (for negative values) or "
                               "after its end (positive values).",
                               EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, CshHintGraphicParam(1)));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE)+"no",
                               "Positive position values are counted from the start "
                               "towards the end, whereas negative values counted from the end towards the start.",
                               EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, CshHintGraphicParam(0)));
    }
    if (csh.AttributeValuesForStyle(std::string(attr), "label"))
        return true;
    return BlockInstruction::AttributeValues(attr, csh, false);
}

BlockInstruction *ArrowLabel::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);
    ArrowLabel *ret = new ArrowLabel(*this, l, prefix);
    ret->AddAttributeList(attr);
    if (mod) {
        if (mod->size())
            chart.Error.Error(mod->front()->action_pos.start, "Arrow labels do not accept content modifiers. Ignoring them.");
        delete mod;
    }
    return ret;
}

void ArrowLabel::ApplyRunningStyle(bool, bool)
{
    style.text += chart.MyCurrentContext().running_style_arrows.read().text;
    if (chart.MyCurrentContext().running_style_arrows.read().numbering)
        style.numbering = chart.MyCurrentContext().running_style_arrows.read().numbering;
    if (chart.MyCurrentContext().running_style_arrows.read().label)
        style.label = chart.MyCurrentContext().running_style_arrows.read().label;
    if (chart.MyCurrentContext().running_style_arrows.read().label_pos)
        style.label_pos = chart.MyCurrentContext().running_style_arrows.read().label_pos;
    if (chart.MyCurrentContext().running_style_arrows.read().label_orient)
        style.label_orient = chart.MyCurrentContext().running_style_arrows.read().label_orient;
    style.marker += chart.MyCurrentContext().running_style_arrows.read().marker;
}


/** Calculate where the label will end up on the arrow, do and do layout.
 * @param [in] canvas The canvas to draw on.
 * @param [in] line The line style of the arrow line.
 *                  We store a pointer to this used later when drawing.
 * @param [in] path The path of the arrow.
 *                  We store a pointer to this used later when drawing.
 * @param [in] len The length of the path (or -1 if not yet calculated)
 * @returns the clip to apply to the arrow line due to the marker.*/
Contour ArrowLabel::LayoutFinalize(Canvas& canvas [[maybe_unused]], const LineAttr& line,
                                   const Path &path, double len)
{
    if (len<0) len = path.GetLength();
    double dist = len*percent/100 + offset;
    pos = path.MovePos(dist, IsFromStart() ? path.GetStartPos() : path.GetEndPos());
    //now dist is nonzero if we need to go before the start (dist<0) or beyond
    //the end (dist>0)
    XY forward_tangent;
    if (dist==0) {
        point = path.GetPoint(pos);
        forward_tangent = path.GetTangent(pos, true, false);
    } else if (dist<0) {
        point = (path.front().PrevTangentPoint(0)-path.front().GetStart()).Normalize()*-dist +
            path.front().GetStart();
        forward_tangent = path.front().NextTangentPoint(0);
    } else {//if (dist>0)
        point = (path.back().NextTangentPoint(1)-path.back().GetEnd()).Normalize()*dist +
            path.back().GetEnd();
        forward_tangent = (path.back().NextTangentPoint(1)-path.back().GetEnd()).Normalize()*(dist+10) +
            path.back().GetEnd(); //10 points further out. Handles the case of dist being very small.
    }
    //Now find which corner (or side half-point) of the label
    //shall we align to 'point'
    XY twh = parsed_label.getTextWidthHeight();
    if (twh.x || twh.y) {
        XY align;
        constexpr double threshold = 1;
        const bool is_horizontal = fabs(point.y - forward_tangent.y) < threshold;
        const bool is_vertical = fabs(point.x - forward_tangent.x) < threshold;
        const bool is_manhattan = is_horizontal || is_vertical;
        //The arrow goes as a slash and not as a backslash at the point of the label
        const bool slants_as_slash = (point.y < forward_tangent.y) == (point.x < forward_tangent.x);
        if (IsTangent(style.label_orient.value_or(EOrientation::Normal))) {
            //In this case we need to produce a label_block that is to be rotated at drawing around 
            //its centroid by label_rotate. Here we reduce the simple cases to non-tangent ones.
            if (is_horizontal) {
                style.label_orient = style.label_orient == EOrientation::Tangent ? EOrientation::Normal : EOrientation::UpsideDown;
                //fallthrough to calculate as if orientation is normal or upside down
                label_rotate = style.label_orient == EOrientation::Normal ? 0 : 180;
            } else if (is_vertical) {
                style.label_orient = (style.label_orient == EOrientation::Tangent) == (point.y < forward_tangent.y)
                    ? EOrientation::Left : EOrientation::Right;
                label_rotate = style.label_orient == EOrientation::Left ? 90 : 270;
                //fallthrough to calculate as if orientation is left or right
            } else {
                const XY tangent = forward_tangent - point;
                XY perp = tangent.Rotate90CW().Normalize()*twh.y/2;
                switch (style.label_pos.value_or(EDirection::Invalid)) {
                default:
                    _ASSERT(0); FALLTHROUGH;
                case EDirection::Invalid:
                    perp = { 0,0 };
                    break;
                case EDirection::Above:
                    if (perp.y > 0) perp = -perp;
                    break;
                case EDirection::Below:
                    if (perp.y < 0) perp = -perp;
                    break;
                case EDirection::Left:
                    if (perp.x > 0) perp = -perp;
                    break;
                case EDirection::Right:
                    if (perp.x < 0) perp = -perp;
                    break;
                }
                label_block = Block(-twh/2, twh/2).Shift(point+perp);
                label_rotate = atan2(tangent.y, tangent.x) * 180 / M_PI; //atan2 returns [-pi, pi]
                if ((style.label_orient == EOrientation::Tangent && fabs(label_rotate) > 90)
                    || (style.label_orient == EOrientation::TangentUpsideDown && fabs(label_rotate) < 90))
                    label_rotate = 180 + label_rotate;
                goto label_block_done;
            }
        }
        if (IsVertical(style.label_orient.value_or(EOrientation::Normal)))
            twh.SwapXY();
        align = twh; //Which point of the label shall we align to 'point'? The current value represents bottom right corner.
        switch (style.label_pos.value_or(EDirection::Invalid)) {
        default:
            _ASSERT(0); FALLTHROUGH;
        case EDirection::Invalid:
            align /= 2; //centroid
            break;
        case EDirection::Above:
            //if horizontal/vertical line, we pick the halfway point of the bottom side
            if (is_manhattan)
                align.x /= 2; //lower midpoint
            else if (slants_as_slash)
                align.x = 0; //bottom-left
            //else //leave bottom-right
            break;
        case EDirection::Below:
            //if horizontal/vertical line, we pick the halfway point of the top side
            if (is_manhattan)
                align = { align.x / 2, 0 }; //upper midpoint
            else if (slants_as_slash)
                align.y = 0; //top right
            else
                align = { 0,0 }; //top left
            break;
        case EDirection::Left:
            //if horizontal/vertical line, we pick the halfway point of the right side
            if (is_manhattan)
                align.y /= 2; //right midpoint
            else if (slants_as_slash)
                align.y = 0; //top-right
            //else //leave bottom-right
            break;
        case EDirection::Right:
            //if horizontal/vertical line, we pick the halfway point of the left side
            if (is_manhattan)
                align = { 0, align.y / 2 }; //left midpoint
            else if (slants_as_slash)
                align.x = 0; //bottom left
            else
                align = { 0,0 }; //top left
            break;
        }
        label_block = Block(XY(0, 0), twh).Shift(point - align);
    label_block_done:
        //If we have line or fill, use the rectangle
        if ((style.f_line && style.line.IsComplete() && !style.line.IsFullyTransparent()) ||
            (style.f_fill && style.fill.color && !style.fill.color->IsFullyTransparent()))
            area = style.line.CreateRectangle_OuterEdge(label_block.CreateExpand(style.line.LineWidth()/2));
        else
            area = LabelCover(); //else the text itself
    }
    area.arc = this;
    //Calculate marker
    has_marker &= style.marker.GetType(EArrowEnd::END)->has_value() || style.marker.GetGvType(EArrowEnd::END)->has_value(); //no marker if no type set.
    if (has_marker) {
        style.marker.MakeComplete();
        style.marker.CreateArrowHeads(chart.arrow_scale);
        arrow_line = &line;
        if (dist==0) {
            arrow_path = &path;
        } else {
            //Create a single straight edge ending in 'point', and make the 'pos' its end
            //This works both for dist<0 and dist>0.
            //Otherwise if 'pos' is not on the path, the arrowhead functions draw nothing.
            extension_path.emplace_back(2*point-forward_tangent, point);
            arrow_path = &extension_path;
            pos = {0,1};
        }
        area += style.marker.endArrowHead.Cover(*arrow_path, pos, true, false, *arrow_line, *arrow_line, false, nullptr);
        return style.marker.endArrowHead.ClipForLine(*arrow_path, pos, true, false, true, *arrow_line, *arrow_line, false, nullptr);
    } else
        return {};
}

void ArrowLabel::PostPosProcess(Canvas &canvas, Chart *c)
{
    chart.IncreaseTotal(area.GetBoundingBox());
    BlockInstruction::PostPosProcess(canvas, c);
    if (!area.IsEmpty())
        chart.AllCovers += area;
}

void ArrowLabel::Draw(Canvas & canvas) const
{
    canvas.Entity(label_block.CreateExpand(style.line.LineWidth()),
                  Label(), style, chart.Shapes); //Don't draw label - Canvas::Entity can only do left-to-right
    DrawLabel(canvas);
    if (has_marker && arrow_path && arrow_line)
        style.marker.endArrowHead.Draw(canvas, *arrow_path, pos, true, false, *arrow_line, *arrow_line, false, nullptr);
}

/** This constructor is used to create a clone.
 * @param [in] a The arrow to copy.
 * @param [in] l The position of the copying action in the input file
 * @param [in] prefix The new name prefix to apply to our name.*/
Arrow::Arrow(const Arrow &a, const FileLineColRange &l, std::string_view prefix) :
    BlockInstruction(a, l, prefix), type(a.type),
    ends(a.ends), route_via(a.route_via), linearextend{ false, false }
{
    if (!valid) return;
    //Now push all locations
    for (auto &e : ends) {
        for (auto &xy : e.pos)
            for (auto &b : xy.blocks)
                b.file_pos.Push(l.start, EInclusionReason::COPY);
        e.file_pos.Push(l.start, EInclusionReason::COPY);
        e.port.file_pos.Push(l.start, EInclusionReason::COPY);
        e.dir.file_pos.Push(l.start, EInclusionReason::COPY);
    }
    //route_via does not need to be handled as it must be empty here...

    //clone the labels
    for (auto &lab : a.labels) {
        auto c = lab->Clone(l, prefix, nullptr, nullptr, nullptr);
        auto dc = dynamic_cast<ArrowLabel*>(c);
        if (dc)
            labels.Append(dc);
        else
            delete c;
    }
    chart.Progress.RegisterBulk(ESections::LAYOUT_ARROWS);
}

/** This constructor is used to create a regular arrow.
 * @param [in] c The chart to add the arrow to.
 * @param [in] n1 The string at the source (tail) of the arrow.
 * @param [in] t The type of the arrow, i.e., bidir or no heads, etc, plus dotted/double/etc.
 * @param [in] n2 The string at the destination (head) of the arrow.
 * @param [in] l The position of the arrow definition.
 * @param [in] l1 The position of 'n1' in the input file.
 * @param [in] l2 The position of 'n2' in the input file.*/
Arrow::Arrow(BlockChart & c, std::string_view n1, ArrowType t, std::string_view n2,
             const FileLineColRange & l, const FileLineColRange & l1, const FileLineColRange & l2) :
    BlockInstruction(c, l), type(t), linearextend{ false, false }

{
    style = chart.MyCurrentContext().styles["arrow"].read();
    style.type = EStyleType::ELEMENT;
    style.block_style_type = EBlockStyleType::Unspecified;
    ApplyRunningStyle(false, false); //parameter values will be ignored
    if (style.text.IsEmpty())
        style.text = chart.MyCurrentContext().text;
    else {
        StringFormat sf = chart.MyCurrentContext().text;
        sf += style.text;
        style.text = sf;
    }
    const char * const arrow_text = ([&t]() {
        switch (t.style) {
        default: _ASSERT(0); FALLTHROUGH;
        case EArrowStyle::SOLID:  return t.start || t.end ? "->" : "--";
        case EArrowStyle::DOTTED: return t.start || t.end ? ">" : "..";
        case EArrowStyle::DASHED: return t.start || t.end ? ">>" : "++";
        case EArrowStyle::DOUBLE: return t.start || t.end ? "=>" : "==";
        }
    }());
	style += chart.MyCurrentContext().styles[arrow_text].read();

    if (!t.end)
        style.arrow.write().SetType(EArrowEnd::END, EArrowType::NONE);
    if (!t.start)
        style.arrow.write().SetType(EArrowEnd::START, EArrowType::NONE);
    display_name = StringFormat::RemovePosEscapesCopy(n1)+arrow_text+StringFormat::RemovePosEscapesCopy(n2);
    ends[0].file_pos = l1.start;
    ends[1].file_pos = l2.start;
    FileLineCol missing_coord;
    unsigned missing_e=0, missing_xy=0; //initialized just to silence gcc complaints
    for (unsigned e = 0; e<2; e++) {
        std::string_view n = e ? n2 : n1;
        if (n.length()==0) {
            valid = false;
            return;
        }
        StringWithPosList offsets;
        if (n[0]=='(') {
            //find closing parenthesis - it may be followed as below
            // (x,y)[@dir[@dist1]]+-dist2
            const size_t closing = std::min(StringFormat::FindVerbatim(n, ")"), n.length());
            //(if no closing, we give the whole to ParseCoord, which will report error.
            auto r = ParseCoord(n.substr(0, closing+1), &chart.Error, &ends[e].file_pos, "arrow");
            if (!r) {
                valid = false;
                return;
            }
            if (r->at(0).blocks.size()==0 && r->at(1).blocks.size()==0) {
                chart.Error.Error(e ? l2.start : l1.start,
                                  "You have to specify at least one coordinate. Ignoring arrow.");
                valid = false;
                return;
            }
            ends[e].is_block = false;
            if (r->at(0).blocks.size()==0 || r->at(1).blocks.size()==0) {
                if (missing_coord.IsValid()) {
                    chart.Error.Error(e ? l2.start : l1.start,
                                      "You can omit only one coordinate from the four. Ignoring arrow.");
                    chart.Error.Error(missing_coord, e ? l2.start : l1.start,
                                      "Here is another omitted coordinate.");
                    valid = false;
                    return;
                }
                missing_coord = e ? l2.start : l1.start;
                missing_e = e;
                missing_xy = r->at(1).blocks.empty();
            } else if (r->at(0).blocks.size()==1 && r->at(1).blocks.size()==1 &&
                       r->at(0).blocks.front().name == r->at(1).blocks.front().name &&
                       !r->at(0).IsParent())
                ends[e].is_block = true;
            ends[e].pos = std::move(*r);
            ends[e].dir.number = -1;
            ends[e].from_coordinate = true;
            n.remove_prefix(closing+1);
            if (n.length()) {
                _ASSERT(n[0]=='@');
                if (n[0]=='@') n.remove_prefix(1);
                if (n.length()) {
                    auto l = StringFormat::Split(n, (e==0 ? l1 : l2).start, "@");
                    if (l.size()>2)
                        chart.Error.Error(l[2].file_pos.start, "You can only specify two '@' symbols after a coordinate. Ignoring the rest.");
                    //take off any offsets at the end
                    offsets = StringFormat::Split(l.back().name, l.back().file_pos.start, "+-", ">>"); //split by potential signs and keep them
                    //If l.back() stars immediately with a number, we will have offsets.front() as empty.
                    if (offsets.front().name.length()==0) offsets.erase(offsets.begin());
                    //move the first of 'offsets' to replace the last of 'l' (what we have split to 'offsets')
                    if (offsets.size()) {
                        l.back() = offsets.front();
                        offsets.erase(offsets.begin());
                    }
                    auto dir = chart.ParseCompassPoint(l[0].name, l[0].file_pos.start, true);
                    if (dir) {
                        if (dir.value()==-2) {
                            chart.Error.Error(l[0].file_pos.start, "The direction 'perp' can only be used in conjunction with a block, not a coordinate. Ignoring it.");
                        } else {
                            static_cast<StringWithPos&>(ends[e].dir) = l[0];
                            ends[e].dir.number = dir.value();
                        }
                    }
                    if (l.size()==2) {
                        double dist = 0;
                        if (from_chars(l[1].name, dist))
                            chart.Error.Error(l[1].file_pos.start, "Internal error. This should be a number.");
                        else
                            ends[e].dir_distance = dist;
                    }
                    //additional offsets may have remained in 'offsets'
                }
            }
        } else { //(not a coord, but a block)
            //create descriptors for the given port and dir and distance.
            //(or the center of the box in the lack thereof)
            //Start with the distance. Note: all numbers in the string guaranteed to start with a sign.
            //Note2: we may have many numbers: one for port, one for compass point, one for distance
            auto l = StringFormat::Split(n, (e==0 ? l1 : l2).start, "@"); //split by potential signs and keep them
            _ASSERT(l.size()<=3);
            if (l.size()>3) {
                chart.Error.Error(l[3].file_pos.start, "Internal error: too many '@' signs in arrow_end. Ignoring after this.");
                l.erase(l.begin()+3, l.end());
            }
            offsets = StringFormat::Split(l.back().name, l.back().file_pos.start, "+-", ">>"); //split by potential signs and keep them
            //If l.back() stars immediately with a number, we will have offsets.front() as empty.
            if (offsets.front().name.length()==0) offsets.erase(offsets.begin());
            //move the first of 'offsets' to replace the last of 'l' (what we have split to 'offsets')
            if (offsets.size()) {
                l.back() = offsets.front();
                offsets.erase(offsets.begin());
            }
            _ASSERT(l.size() && l.front().name.length() && isalpha(l.front().name[0]));
            //Now l[] contains name, port, dir (last 2 optional)
            //and l2[] contains the numbers after (potentially x<num>, y<num>)

            //OK, now parse name@port@dir, or name@port or name
            if (l.size()>=3)
                ends[e].dir = StringWithPosNumber(std::move(l[2]), -1);
            else
                ends[e].dir.number = -1;
            if (l.size()>=2)
                ends[e].port = std::move(l[1]);
            for (unsigned xy = 0; xy<2; xy++) {
                ends[e].pos[xy].blocks.push_back(l.front());
                ends[e].pos[xy].edge = { 0.5, false, 0 }; //prio and justify is not used in ".edge"
            }
            ends[e].is_block = true;
            ends[e].from_coordinate = false;
        }
        ends[e].distance_pixel = 0;
        ends[e].distance_percent = 0;
        ends[e].offset_pixel = { 0,0 };
        ends[e].offset_pixel_user = { 0,0 };
        ends[e].offset_percent = { 0,0 };
        //Cycle through the added numbers.
        for (auto &sp : offsets) {
            _ASSERT(sp.name.length() && (sp.name[0]=='+' || sp.name[0]=='-'));
            double *dbl;
            std::string_view num = sp.name;
            num.remove_prefix(1);
            if (sp.name.back()=='%' && ends[e].is_block) {
                num.remove_suffix(1);
                if (sp.name[1]=='x') { dbl = &ends[e].offset_percent.x; num.remove_prefix(1); }
                else if (sp.name[1]=='y') { dbl = &ends[e].offset_percent.y; num.remove_prefix(1); }
                else dbl = &ends[e].distance_percent;
            } else {
                if (sp.name.back()=='%') {
                    //This must have been a coordinate
                    num.remove_suffix(1);
                    chart.Error.Error(sp.file_pos.start, "Coordinates cannot be augmented by a percentage offset. Taking it as a pixel offset.");
                }
                if (sp.name[1]=='x') { dbl = &ends[e].offset_pixel_user.x; num.remove_prefix(1); }
                else if (sp.name[1]=='y') { dbl = &ends[e].offset_pixel_user.y; num.remove_prefix(1); }
                else dbl = &ends[e].distance_pixel;
            }
            double tmp;
            if (from_chars(num, tmp)) {
                if (ends[e].is_block)
                    chart.Error.Error(sp.file_pos.start, "I expect a number, a percentage, +-x<num>, +-y<num>, +-x<num>% or +-y<num>% here and got '"+sp.name+"'. Ignoring this component.");
                else
                    chart.Error.Error(sp.file_pos.start, "I expect a number, +-x<num> or +-y<num> here and got '"+sp.name+"'. Ignoring this component.");
            } else {
                if (sp.name[0]=='+') *dbl += tmp;
                else *dbl -= tmp;
            }
        }
    }
    //If the user omitted one coordinate use the one at the other end
    if (missing_coord.IsValid())
        ends[missing_e].pos[missing_xy] = ends[1-missing_e].pos[missing_xy];

    chart.Progress.RegisterBulk(ESections::LAYOUT_ARROWS);
}

Arrow::~Arrow() {
    chart.Progress.RegisterBulk(ESections::LAYOUT_ARROWS, -1);
}

bool Arrow::AddAttribute(const Attribute &a)
{
    if (a.Is("via")) {
        if (a.value.length()==0) return true;
        if (a.value.front()=='(') {
            auto coord = ParseCoord(a.value, &chart.Error, &a.linenum_value.start, "attribute");
            if (coord) {
                if (coord->at(0).blocks.size()==0 || coord->at(1).blocks.size()==0) {
                    chart.Error.Error(a, true,
                                      "You have to specify both coordinates. Ignoring attribute.");
                    return true;
                }
                route_via.push_back(std::move(*coord));
            }
            return true;
        }
        auto at = a.value.find_last_of('@');
        if (at == std::string::npos) {
            route_via.push_back(ViaAttrSoloBlock{a.value, a.linenum_value.start, ViaAttrSoloBlock::CENTER});
            return true;
        }
        ViaAttrSoloBlock via;
        if (CaseInsensitiveEqual(a.value.c_str()+at+1, "top"))
            via.pos = ViaAttrSoloBlock::TOP;
        else if (CaseInsensitiveEqual(a.value.c_str()+at+1, "bottom"))
            via.pos = ViaAttrSoloBlock::BOTTOM;
        else if (CaseInsensitiveEqual(a.value.c_str()+at+1, "left"))
            via.pos = ViaAttrSoloBlock::LEFT;
        else if (CaseInsensitiveEqual(a.value.c_str()+at+1, "right"))
            via.pos = ViaAttrSoloBlock::RIGHT;
        else if (CaseInsensitiveEqual(a.value.c_str()+at+1, "center"))
            via.pos = ViaAttrSoloBlock::CENTER;
        else if (CaseInsensitiveEqual(a.value.c_str()+at+1, "middle"))
            via.pos = ViaAttrSoloBlock::CENTER;
        else if (CaseInsensitiveEqual(a.value.c_str()+at+1, "topleft"))
            via.pos = ViaAttrSoloBlock::TOPLEFT;
        else if (CaseInsensitiveEqual(a.value.c_str()+at+1, "topright"))
            via.pos = ViaAttrSoloBlock::TOPRIGHT;
        else if (CaseInsensitiveEqual(a.value.c_str()+at+1, "bottomleft"))
            via.pos = ViaAttrSoloBlock::BOTTOMLEFT;
        else if (CaseInsensitiveEqual(a.value.c_str()+at+1, "bottomright"))
            via.pos = ViaAttrSoloBlock::BOTTOMRIGHT;
        else {
            const std::string e = a.value.substr(at+1);
            if (e.length())
                chart.Error.Error(FileLineColRange(a.linenum_value).IncStartCol(at+1).start,
                               "Don't understand the text after the '@' ('"+e+"'). Ignoring Attribute.",
                               "Expecting 'top'/'bottom'/'left'/'right' or 'center'.");
            else
                chart.Error.Error(FileLineColRange(a.linenum_value).IncStartCol(at+1).start,
                                   "Missing text after the '@' ('"+e+"'). Ignoring Attribute.",
                                   "Expecting 'top'/'bottom'/'left'/'right' or 'center'.");
            a.error = true;
            return true;
        }
        via.block = a.value.substr(0, at);
        via.block_pos = a.linenum_value.start;
        route_via.push_back(via);
        return true;
    }
    if (a.Is("routing.order")) {
        if (a.type == EAttrType::CLEAR)
            route_order = 0;
        else if (a.CheckType(EAttrType::NUMBER, chart.Error)) {
            route_order = a.number;
        }
        return true;
    }
    return BlockElement::AddAttribute(a);
}


void Arrow::AttributeNames(Csh &csh)
{
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"via",
                           "Specify a point through which via the arrow shall be routed.",
                           EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"routing.order",
                           "Specify ordering for arrows overlapping.",
                           EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
    csh.AttributeNamesForStyle("arrow");
    BlockInstruction::AttributeNames(csh, false);
}

const char * const ViaAttrSoloBlock::route_attr_values[] = {"invalid", nullptr,
    "<block>@top", "Route around the top of <block>.",
    "<block>@bottom", "Route around the bottom of <block>.",
    "<block>@left", "Route around the left of <block>.",
    "<block>@right", "Route around the right of <block>.",
    "<block>@topleft", "Route around the top left corner of <block>.",
    "<block>@bottomleft", "Route around the bottom left corner of <block>.",
    "<block>@topright", "Route around the top right corner of <block>.",
    "<block>@bottomright", "Route around the bottom right corner of <block>.",
    ""};

/** Callback for drawing a symbol before via postfixes in hintboxes.
 * @ingroup hintpopup_callbacks*/
bool block::CshHintGraphicCallbackForVia(Canvas *canvas, CshHintGraphicParam p, CshHintStore&)
{
    if (!canvas) return false;
    const double r = 0.35;
    const Block b(HINT_GRAPHIC_SIZE_X*r, HINT_GRAPHIC_SIZE_X*(1-r), HINT_GRAPHIC_SIZE_Y*r, HINT_GRAPHIC_SIZE_Y*(1-r));
    FillAttr fill(ColorType::lgray(), ColorType::white(), EGradientType::OUTWARD);
    LineAttr line(ELineType::SOLID, ColorType::black(), 0.5, ECornerType::NONE, 0);
    canvas->Fill(b, fill);
    canvas->Line(b, line);
    const XY xy = [p]()->XY {
        switch (int(p)) {
        default: _ASSERT(0); FALLTHROUGH;
        case 1: return {0, -1};
        case 2: return {0, +1};
        case 3: return {-1, 0};
        case 4: return {+1, 0};
        case 5: return {-1, -1};
        case 6: return {-1, +1};
        case 7: return {+1, -1};
        case 8: return {+1, +1};
        }
    }().Scale({HINT_GRAPHIC_SIZE_X*0.35, HINT_GRAPHIC_SIZE_Y*0.35})+b.Centroid();
    canvas->Fill(Contour(xy, 2), FillAttr::Solid(ColorType::red()));
    return true;
}


bool Arrow::AttributeValues(std::string_view attr, Csh &csh)
{
    if (CaseInsensitiveEqual(attr, "via")) {
        csh.AddToHints(ViaAttrSoloBlock::route_attr_values, csh.HintPrefixNonSelectable(), EHintType::ATTR_VALUE,
                       CshHintGraphicCallbackForVia, false);
        return true;
    }
    if (CaseInsensitiveEqual(attr, "routing.order")) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<number>",
                               "When arrows starting from a block overlap they are shifted in the order "
                               "they are defined. Then they are laid out again in the order specified by this number, "
                               "which defaults to zero.",
                               EHintType::ATTR_VALUE, false));
        csh.AddToHints(ViaAttrSoloBlock::route_attr_values, csh.HintPrefixNonSelectable(), EHintType::ATTR_VALUE,
                       CshHintGraphicCallbackForVia, false);
        return true;
    }
    if (csh.AttributeValuesForStyle(std::string(attr), "arrow"))
        return true;
    return BlockInstruction::AttributeValues(attr, csh, false);
}

bool Arrow::AmIContent() const
{
    for (unsigned u = 0; u<=1; u++) {
        for (auto &e : ends)
            if (e.pos[u].IsParent())
                return true;
        for (auto &v : route_via)
            if (v.index()==1 && std::get<1>(v)[0].IsParent())
                return true;
    }
    return false;
}

void Arrow::AddLabels(const BlockInstrList *l)
{
    //First turn any potential label in style to an ArrowLabel in 'labels'
    if (style.label->length()) {
        labels.push_back(std::make_unique<ArrowLabel>(chart,
                                                      style.label_align.value_or(0.5)*100, 0,
                                                      FileLineColRange(style.label.file_pos, style.label.file_pos)));
        //Now add any potential attributes
        labels.back()->style.text += style.text; //this will completely overwrite the text coming from style 'label'
        labels.back()->style.label = std::move(style.label);
        style.label = {{}, {}}; //kill original label, so that numbering etc, does not get confused in PostParseProcess and FinalizeLabel
        labels.back()->style.label_pos = style.label_pos;
        labels.back()->style.label_orient = style.label_orient;
        labels.back()->style.marker = style.marker;
        labels.back()->style.marker.CreateArrowHeads(chart.arrow_scale);
        labels.back()->has_marker = !labels.back()->style.marker.endArrowHead.IsNone();
    }
    if (l) for (auto &pInstr : *l) {
        auto pLabel = dynamic_cast<ArrowLabel*>(pInstr.get());
        if (pLabel)
            //Invoke copy constructor for the label
            labels.Append(std::make_unique<ArrowLabel>(*pLabel));
        else {
            _ASSERT(0); //by now we should only have labels in the instruction list
        }
    }
}

BlockInstruction *Arrow::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);
    Arrow *ret = new Arrow(*this, l, prefix);
    ret->AddAttributeList(attr);
    if (mod) {
        if (mod->size())
            chart.Error.Error(mod->front()->action_pos.start, "Arrows do not accept content modifiers. Ignoring them.");
        delete mod;
    }
    return ret;
}

void Arrow::ApplyRunningStyle(bool cloning, bool recursive)
{
    style += chart.MyCurrentContext().running_style_arrows.read();
    if (cloning)
        //irrespective of recursive, all our label shall have these changes
        for (auto &p:labels)
            p->ApplyRunningStyle(cloning, recursive);
}

bool Arrow::PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number)
{
    for (unsigned e = 0; e<2; e++) {
        for (unsigned xy = 0; xy<2; xy++) {
            if (ends[e].pos[xy].IsParent())
                continue;
            if (Resolve(ends[e].pos[xy].blocks, {nullptr, nullptr, nullptr, nullptr, parent},
                        "Block '%b' is a 'break' block that occupies no area. Ignoring arrow.",
                        {}, "Cannot resolve '%b' for arrows. Use the name of a block. Ignoring arrow.",
                        nullptr, nullptr) ||
                CheckExists(ends[e].pos[xy].blocks, "arrow", { HandleHidden::Keep })) { //resolve hidden endpoints in Layout
                valid = false;
                return false;
            }
        }
        //if we have a port name, parse that to coords. Now we surely have the block.
        if (ends[e].port.name.length()!=0) {
            //if we have port name, resolve
            auto b = chart.GetBlockByName(ends[e].pos[0].blocks.front().name, {}, true, true)[0];
            auto port = b->GetPort(ends[e].port.name);
            if (port) {
                ends[e].pos[0].edge = port.value().xy[0];
                ends[e].pos[1].edge = port.value().xy[1];
                ends[e].dir.number = port.value().dir; //default compass point
                if (ends[e].dir.name.length()) {
                    auto dir = chart.ParseCompassPoint(ends[e].dir.name, ends[e].dir.file_pos.start, true);
                    if (dir)
                        ends[e].dir.number = dir.value();
                }
            } else {
                //did not find port. It is always an error, since compass points are also port names.
                chart.Error.Error(ends[e].port.file_pos.start,
									StrCat("Did not recognize port '",
											ends[e].port.name, "'. Ignoring it."));
                ends[e].port.name.clear();
            }
        }
    }
    if (!style.route_cross) style.route_cross.emplace();
    if (Resolve(*style.route_cross, {nullptr, nullptr, nullptr, nullptr, parent}, {}, {},
                "Cannot resolve '%b' for arrows. Use the name of a block. Ignoring arrow.",
                nullptr, nullptr) ||
        CheckExists(*style.route_cross, "arrow", {HandleHidden::Remove})) {
        valid = false;
        return false;
    }
    if (!style.route_cross_parent) style.route_cross_parent.emplace();
    if (Resolve(*style.route_cross_parent, {nullptr, nullptr, nullptr, nullptr, parent}, {}, {},
                "Cannot resolve '%b' for arrows. Use the name of a block. Ignoring arrow.",
                nullptr, nullptr) ||
        CheckExists(*style.route_cross_parent, "arrow", { HandleHidden::Remove })) {
        valid = false;
        return false;
    }
    if (!style.distance_per_block) style.distance_per_block.emplace();
    if (Resolve(*style.distance_per_block, {nullptr, nullptr, nullptr, nullptr, parent}, {}, {},
                "Cannot resolve '%b' for arrows. Use the name of a block. Ignoring arrow.",
                nullptr, nullptr) ||
        CheckExists(*style.distance_per_block, "arrow", {HandleHidden::Remove}, {}, true)) {
        valid = false;
        return false;
    }
    for (auto &v : route_via) {
        switch (v.index()) {
        default:
            _ASSERT(0);
            break;
        case 0:
            if (chart.GetBlockByNameWithError(std::get<0>(v).block, std::get<0>(v).block_pos,
                                              parent, "attribute"))
                continue;
            break;
        case 1:
            if (!std::get<1>(v)[0].IsParent())
                if (Resolve(std::get<1>(v)[0].blocks, {nullptr, nullptr, nullptr, nullptr, parent},
                            "Block '%b' is a 'break' block that occupies no area. Ignoring 'via' attribute.",
                            {}, "Cannot resolve '%b' for arrows. Use the name of a block. Ignoring 'via' attribute.",
                            nullptr, nullptr) ||
                    CheckExists(std::get<1>(v)[0].blocks, "attribute", { HandleHidden::Remove }))
                    break;
            if (!std::get<1>(v)[1].IsParent())
                if (Resolve(std::get<1>(v)[1].blocks, {nullptr, nullptr, nullptr, nullptr, parent},
                        "Block '%b' is a 'break' block that occupies no area. Ignoring 'via' attribute.",
                        {}, "Cannot resolve '%b' for arrows. Use the name of a block. Ignoring 'via' attribute.",
                        nullptr, nullptr) ||
                CheckExists(std::get<1>(v)[1].blocks, "attribute", { HandleHidden::Remove }))
                break;
            continue;
        }
        v = ViaAttrSoloBlock();
    }
    remove_if(route_via, [](auto &v) {return v.index()==0 && std::get<0>(v).block.length()==0; });
    //If we start and end from the same points
    if (ends[0].pos[0].IsSame(ends[1].pos[0]) && ends[0].pos[1].IsSame(ends[1].pos[1]) && route_via.size()==0) {
        //OK, this is an a->a arrow. Add compass points later in Layout().
        //Now just check if our routing allows curves.
        if (style.routing==EArrowRouting::Horizontal ||
            style.routing == EArrowRouting::Vertical ||
            style.routing==EArrowRouting::Grid ||
            style.routing==EArrowRouting::Straight) {
            chart.Error.Error(file_pos.start, StrCat("Cannot use ",
                                                     style.routing==EArrowRouting::Horizontal ? "horizontal" :
                                                     style.routing==EArrowRouting::Vertical ? "vertical" : 
                                                     style.routing==EArrowRouting::Grid ? "grid" : "straight",
                                                     " routing for an arrow/line starting and ending at the same block ('",
                                                     ends[0].pos[0].blocks.front().name, "'). Ignoring arrow."));
            valid = false;
            return false;
        }
    }
    BlockElement::PostParseProcess(canvas, hide_by, number);
    if (!style.distance) //Set default distance now that we know what routing we apply.
        style.distance = style.routing==EArrowRouting::Manhattan ? BlockChart::def_distance_manhattan : BlockChart::def_distance;
    if (!style.route_arrow_distance) //Set default arrow distance now that we know what routing we apply. It is set to zero if the used don't want de-overlapping.
        style.route_arrow_distance = style.distance;
    style.arrow.read().CreateArrowHeads(chart.arrow_scale);
    for (auto &l:labels)
        if (!l->PostParseProcess(canvas, hide_by, number))
            l.reset();
    labels.remove(nullptr);

    for (unsigned u = 0; u < 2; u++)
        ends[u].offset_pixel = ends[u].offset_pixel_user;

    //Register us for the chart
    chart.Arrows.push_back(this);
    my_index = chart.Arrows.size()-1;
    return true;
}

void Arrow::FinalizeLabel(Canvas &canvas)
{
    BlockInstruction::FinalizeLabel(canvas);
    for (auto &l:labels)
        l->FinalizeLabel(canvas);
}

/** Creates the constraints based on our attributes and adds them to the chart.
 * For arrows, we merely return the max coordinates of our ends and vias
 * if they were specified in relation to the parent.
 * @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.
 * @returns max coordinates of us (if any).*/
BlockInstruction::AddConstraintResult
Arrow::AddConstraints(Canvas&, ConstraintSet&, unsigned from, unsigned to,
                      unsigned[2],
                      const InternalAlignment*[2]) const
{
    AddConstraintResult ret;
    for (unsigned u = from; u<=to; u++) {
        for (auto &e : ends)
            if (e.pos[u].IsParent() && e.pos[u].edge.offset) {
                //if we have say 70%+10, this means the remaining 30% must be at least 10 pixels
                //if we have sat 40%-10, this means the 40% must be at least 10 pixels long
                const double size = e.pos[u].edge.offset > 0 ?
                                       e.pos[u].edge.offset/(1-e.pos[u].edge.pos) :
                                      -e.pos[u].edge.offset/e.pos[u].edge.pos;
                if (e.pos[u].edge.margin)
                    ret[u].max_to_visible = std::max(ret[u].max_to_visible, size);
                else
                    ret[u].max_to_imargin = std::max(ret[u].max_to_imargin, size);
            }
        for (auto &v : route_via)
            //do the same thing as above for hard vias (only they can contain this kind of absolute coord)
            if (v.index()==1 && std::get<1>(v)[u].IsParent() && std::get<1>(v)[u].edge.offset) {
                const double size = std::get<1>(v)[u].edge.offset > 0 ?
                                       std::get<1>(v)[u].edge.offset/(1-std::get<1>(v)[u].edge.pos) :
                                      -std::get<1>(v)[u].edge.offset/std::get<1>(v)[u].edge.pos;
                if (std::get<1>(v)[u].edge.margin)
                    ret[u].max_to_imargin = std::max(ret[u].max_to_imargin, size);
                else
                    ret[u].max_to_visible = std::max(ret[u].max_to_visible, size);
            }
    }
    return ret;
}

enum class EnclosureResult {
    Ok, ///<They can be connected
    First_in, ///<The first one is in an enclosure, while the second is not
    Second_in, ///<The second one is in an enclosure, while the first is not
    Different  ///<Both are in enclosures, but in different ones.
};

/** Checks if a path is possible between two ends of an arrow without
 * crossing anything in 'avoid'.
 * Note: This function returns OK, if both p1 and p2 are on the edge of a hole inside
 * 'avoid'. Now holes may touch and if, say, p1 is on such a touchpoint, it may be
 * simultaneously on the edge of two holes. So we cater for that. */
EnclosureResult CheckArrowEnclosure(const Contour &avoid, const XY &p1, const XY &p2) {
    for (unsigned u = 0; u<avoid.size(); u++) {
        const bool first_in = avoid[u].Outline().IsWithin(p1)==contour::WI_INSIDE;
        if (first_in != (contour::WI_INSIDE==avoid[u].Outline().IsWithin(p2)))
            return first_in ? EnclosureResult::First_in : EnclosureResult::Second_in;
        if (first_in) {
            //OK, both are inside, search for the hole they are in.
            //Find all holes (which may touch), p1 is inside or on the edge of
            bool p1_was_in_a_hole = false;
            for (const auto &h : avoid[u].Holes()) {
                //The below checks return true if p1/p2 are inside or on the edge of 'h'
                //If the two points are on the
                const bool first_in_hole = h.Outline().IsWithin(p1)!=contour::WI_OUTSIDE;
                if (!first_in_hole) continue;
                p1_was_in_a_hole = true;
                if (contour::WI_OUTSIDE!=h.Outline().IsWithin(p2)) {
                    //Both are in or on the edge of this hole
                    Contour blocks_in_hole;
                    for (const auto &b : h.Holes()) //Take the shapes inside the hole both p1 and p2 are part of
                        blocks_in_hole += Contour(b); //TODO: This is now an unnecessary copy of 'b'.
                    return CheckArrowEnclosure(blocks_in_hole, p1, p2);
                }
            }
            //p1 was in or on the edge of one or more holes, but p2 was in or on the edge
            //of none of them.
            if (p1_was_in_a_hole) return EnclosureResult::Different;
            else return EnclosureResult::First_in; //p1 is inside the coverage of avoid[u] and in none of its holes
        }
    }
    return EnclosureResult::Ok;
}

void CalculateIndex(std::array<std::optional<Contour>, 16> &C, size_t index) {
    if (index==0) return;
    if (C[index].has_value()) return;
    _ASSERT((index & (index - 1))); //index is not a power of 2
    //search for the highest bit set in index
    for (size_t bit = C.size()/2; bit; bit /= 2)
        if (index & bit) {
            CalculateIndex(C, index - bit);
            _ASSERT(C[bit].has_value());
            C[index] = *C[index-bit] + *C[bit];
            return;
        }
    _ASSERT(0);
}

/** Takes a set of block covers. Detects if p1 and p2 can be connected.
 * If not it re-tries with bbs_blocks (blocks without arrows).
 * If the re-try is OK, it gives the appropriate warning and returns success
 * with the remaining blocks in bbs_blocks. Else returns true and emits an error.
 * If yes, it removes from blocks those elements that cannot touch an
 * arrow from p1->p2 (because, e.g., p1&p2 are in a hole, so all blocks
 * not in that hole are irrelevant. Pruning is to increase arrow layout
 * performance later.
 * The caller shall provide appropriate file locations and the description
 * of the two points (e.g., "its start", "its end", or "one of its 'via' attributes")*/
//Returns the highest index that can be done
unsigned Arrow::CheckSegmentRoutability(std::array<std::optional<Contour>, 16> &C, unsigned index,
                                    const XY &p1, const XY &p2,
                                    std::string_view t1, std::string_view t2,
                                    const FileLineCol &l1, const FileLineCol &l2) const {
    //Shows which index to try next.
    //The lower bits have precedence so we try removing them later.
    //bit 0 is mandatory.
    static constexpr unsigned next[16] = {
        0b0000, 0b0000, 0b0000, 0b0101,  0b0000, 0b1001, 0b0000, 0b1011,
        0b0000, 0b0001, 0b1000, 0b1101,  0b0001, 0b0000, 0b0011, 0b0111
    };
    EnclosureResult res = EnclosureResult::Ok;
    for (; index; index=next[index]) {
        if (!C[index].has_value()) CalculateIndex(C, index);
        res = CheckArrowEnclosure(*C[index], p1, p2);
        if (res == EnclosureResult::Ok) return index;
    }
    switch (res) {
    default:
    case EnclosureResult::Ok: _ASSERT(0); return index;
    case EnclosureResult::Different:
        //They are in different holes
        if (path.IsEmpty()) { //if this is a re-ty and we already have a path, don't emit the error.
            chart.Error.Error(l1, StrCat("Cannot route this arrow, because ", t1, " and ",
                                         t2, " are in different enclosures."));
            chart.Error.Error(l2, l1, "Here is the other point.");
        }
        return 0;
    case EnclosureResult::First_in:
    case EnclosureResult::Second_in:
        if (path.IsEmpty()) { //if this is a re-ty and we already have a path, don't emit the error.
            const bool fw_arrow = !style.arrow.read().endArrowHead.IsNone() &&
                style.arrow.read().startArrowHead.IsNone();
            const bool bw_arrow = style.arrow.read().endArrowHead.IsNone() &&
                !style.arrow.read().startArrowHead.IsNone();
            const bool first_in = res==EnclosureResult::First_in;
            if ((res==EnclosureResult::First_in && fw_arrow) || (res==EnclosureResult::Second_in && bw_arrow)) {
                //use this error only in case of a directional arrow going outside
                chart.Error.Error(res==EnclosureResult::First_in ? l1 : l2,
                                  StrCat("Cannot route this arrow, because ",
                                         first_in ? t1 : t2,
                                         " is inside an enclosure and cannot reach to ",
                                         first_in ? t2 : t1, "."));
                chart.Error.Error(first_in ? l2 : l1, first_in ? l1 : l2,
                                  "Here is the other point.");
            } else {
                //bidir, line arrows or one going inside the enclosure use this error
                chart.Error.Error(res==EnclosureResult::First_in ? l1 : l2,
                                  StrCat("Cannot route this arrow, because ",
                                         first_in ? t1 : t2,
                                         " is inside an enclosure and is not reachable from ",
                                         first_in ? t2 : t1, "."));
                chart.Error.Error(first_in ? l2 : l1, first_in ? l1 : l2,
                                  "Here is the other point.");
            }
        }
        return 0;
    }
}

namespace block
{

/** Creates a copy of the list l. If i is a valid pointer to any element, an iterator
 * in the new list to the corresponding element is also returned, else the end() of the new list.*/
template <typename T>
std::tuple<contour::EdgeList<T>, typename contour::EdgeList<T>::iterator>
CopyList(const contour::EdgeList<T> &l, typename contour::EdgeList<T>::iterator i)
{
    std::tuple<contour::EdgeList<T>, typename contour::EdgeList<T>::iterator> ret;
    std::get<1>(ret) = std::get<0>(ret).end();
    for (auto j = l.begin(); j!=l.end(); j++) {
        auto k = std::get<0>(ret).insert(std::get<0>(ret).end(), *j);
        if (j==i)
            std::get<1>(ret) = k;
    }
    return ret;
}

struct EdgeWithSide : public contour::Edge
{
    using Edge::Edge;
    EdgeWithSide(const Edge &e) : Edge(e) {}
    ESide block_side = ESide::INVALID; //Invalid value means a free edge, END means the original, LEFT/RIGHT means we go around a block on that side of us
    bool check_for_overlap = true;  //This is an edge that may cross a block and we need to check for that
    std::array<double, 2> ratio;    //This is the ration of the distance of the control point to the length of the edge at smoothing.
    bool CheckAndCombine(const EdgeWithSide &next, double tolerance)
    {
        if (block_side!=next.block_side) return false;
        //if (check_for_overlap!=next.check_for_overlap) return false;
        return Edge::CheckAndCombine(next, tolerance);
    }
};

/** A path with a calculated length.
 * We also maintain a sum of total direction changes. */
struct PathCandidate : PathScore  //we do not use 'index' and 'shift' during Layout()
{
    contour::EdgeVector<EdgeWithSide> path;
    PathCandidate(contour::EdgeVector<EdgeWithSide> &&p) : path(std::move(p)) { CalcualteLengthTurn(); }
    template <typename EdgeContainer>
    PathCandidate(EdgeContainer &&p)
    {
        path.reserve(p.size());
        for (auto &e: p)
            path.push_back(Edge(e));
        CalcualteLengthTurn();
    }
    void CalcualteLengthTurn() {
        length = path.GetLength();
        turn = std::accumulate(path.begin(), path.end(), 0., [](double t, auto &e) {return t+Turn(e); });
        if (path.size()>1) for (auto i = path.begin()+1; i!=path.end(); i++) turn += Turn(*std::prev(i), *i);
    }
    PathCandidate operator + (PathCandidate &&o) && 
    {
        const double t = path.size()&&o.path.size() ? Turn(path.back(), o.path.front()) : 0;
        contour::EdgeVector<EdgeWithSide> tmp(std::move(path));
        tmp.append(std::move(o.path));
        return PathCandidate(std::move(tmp), { index & o.index, length + o.length, turn + o.turn + t, shift + o.shift });
    }
    void UpdateTurnsLengthWithWaypoints(bool is_end, const XY& true_end) {
        if (path.empty()) return;
        const XY& end = is_end ? path.back().GetEnd() : path.front().GetStart();
        if (true_end.test_equal(end)) return;
        if (is_end) {
            Edge E(path.back().GetEnd(), true_end);
            turn += Turn(path.back(), E);
            length += E.GetLength();
        } else {
            Edge E(true_end, path.front().GetStart());
            turn += Turn(E, path.front());
            length += E.GetLength();
        }
    }
protected:
    PathCandidate(contour::EdgeVector<EdgeWithSide> &&p, const PathScore &s) : PathScore(s), path(std::move(p)) {}
};


/** Returns the position of (one of) the leftmost/rightmost or topmost/bottommost point of the path */
template<bool y, typename iter>
std::array<std::pair<XY, std::pair<iter, double>>, 2> Extremes(iter begin, iter end)
{
    std::array<std::pair<XY, std::pair<iter, double>>, 2> ret;
    _ASSERT(!(begin==end));
    if (begin==end) return ret;
    auto r = begin->Extremes(y);
    ret[0] = { r[0].first,{ begin, r[0].second } };
    ret[1] = { r[1].first,{ begin, r[1].second } };
    while (!(++begin==end)) {
        auto r = begin->Extremes(y);
        if (r[0].first[y] < ret[0].first[y])
            ret[0] = { r[0].first,{ begin, r[0].second } };
        if (r[1].first[y] > ret[1].first[y])
            ret[1] = { r[1].first,{ begin, r[1].second } };
    }
    return ret;
}

//Returns true if the (origin->P) is more horizontal than vertical
inline bool is_horizontal(const XY &p) noexcept { return fabs(p.y) < fabs(p.x); }
inline bool is_horizontal(const XY &p, const XY &p1) noexcept { return is_horizontal(p1-p); }
inline bool is_horizontal(const Edge &e) noexcept { return is_horizontal(e.GetStart(), e.GetEnd()); }

/** Create a Manhattan routed path around 'c' between 2 of its crosspoints.
 * It is allowed to cut into 'c' (we will continue to
 * That is only horizontal or vertical edges can be used.
 * The 'me' member of cp1 and cp2 refers to 'c'.
 * We assume c is not empty. */
std::array<Path, 2> ManhattanRound(const SimpleContour &c, const XY &cp1, const XY &cp2,
                                   bool clockwise, bool counterclockwise) {
    /* Here is some graphics of what we do
     *
     *          |             The * shape is 'c'. The | line is the edge we cut, with the two CPs
     *      **  |             marked as 'x'. We cut he swipe of the * shape between the two CPs
     *    .*..**+***......    plus extending left/right and then we get the bounding box.
     *    *.....|...*****.
     *    *.....|........*    The bounding box of the cut is the dotted area. That bb contour is
     *    *.....|...*****.    what we will follow around 'c' instead of going from cp1->cp2 directly.
     *    .*..**+***......
     *     *  * |                         Then we continue to check if the two paths (the halves of
     *   **   * |                         the bounding box themselves cross the contour or not.
     *  *     **|*******************      If so, we repeat the above procedure, but only take one of
     *  *       |                  *      the paths.
     *   *******|*******************
     *          |
     */
    /* 'horizontal' is true, if cut is more horizontal than vertical.
     *      +--------+     It can actually be slanted,
     *      |        |     e.g., like below, when two edges (...)
     *   ...*....    |     cross a rectangle (---). The two crosspoints
     *      |   .    |     are marked with *.
     *      +---*----+
     *          .
     */
    const bool horizontal = is_horizontal(cp1, cp2);
    const bool cp1_to_cp_grows = cp1[!horizontal] < cp2[!horizontal];
    const Range r_my{std::min(cp1[!horizontal], cp2[!horizontal]), std::max(cp1[!horizontal], cp2[!horizontal])};
    const Range r_other = horizontal ? c.GetBoundingBox().y : c.GetBoundingBox().x;
    const Block cut_with = horizontal ? Block(r_my, r_other) : Block(r_other, r_my);
    const Contour cut = Contour(c)*cut_with; //TODO: This is now an unnecessary copy of 'c'.
    /* If 'cut' fell into separate pieces, get the ones, which we overlap
     * +----------------+
     * |                |     If the two stars are the two crosspoints and
     * |     +----------+     we decide it is a horizontal cut, we will end up
     * |     |                with 'cut' as the two boxes filled below.
     * |     |    +-----+     So we need to select the one, which has the first
     * |     |    |     |     cp in it and take the bounding box of it.
     * |     |  ..*...  |
     * |     |    |  :  |     For the sake of performance, we simply sum
     * |     +----+  :  |     all the bounding boxes of chunks, whose bounding box
     * |             :  |     includes the first cp.
     * +-------------*--+
     *               :
     *
     *            +-----+
     *            |@@@@@|
     *            +-----+
     *
     *            +-----+
     *            |@@@@@|
     *          ..*...@@|
     *            |@@:@@|
     *            +@@:@@|
     *            |@@:@@|
     *            +--*--+
     *               :
     *
     */
    Range result(true);
    const double cp1_coord = cp1[horizontal];
    for (const auto &c : cut)
        if (const Range &bb_r = (horizontal ? c.GetBoundingBox().y : c.GetBoundingBox().x);
            bb_r.IsWithinBool(cp1_coord))
            result += bb_r;
    std::array<Path, 2> ret;
    if (!result.IsInvalid())
        for (unsigned u=0; u<2; u++)
            if (std::array<bool, 2>{clockwise, counterclockwise}[u]) {
                if (horizontal) {
                    const double Y = cp1_to_cp_grows!=bool(u) ? result.from : result.till; //!= is boolean xor
                    if (!contour::test_equal(cp1.y, Y))
                        ret[u] = Path{cp1, XY(cp1.x, Y), XY(cp2.x, Y), cp2};
                } else {
                    const double X = cp1_to_cp_grows!=bool(u) ? result.till : result.from;
                    if (!contour::test_equal(cp1.x, X))
                        ret[u] = Path{cp1, XY(X, cp1.y), XY(X, cp2.y), cp2};
                }
            }
    return ret;
}

//Check the range [first, first+num) for side similarity.
//We return ESide::END, if all edges are ESide::END; ESide::LEFT or RIGHT
//if all edges are either ESide::END or LEFT/RIGHT respectively; and
//ESide::INVALID if LEFT and RIGHT edges both appear or any of the
//edges is ESide::INVALID.
template <typename iter>
ESide IsUniformSide(iter first, size_t num) {
    ESide ret = first->block_side;
    for (++first; num>1; ++first, --num)
        if (first->block_side==ret) continue;
        else if (first->block_side==ESide::END) continue;
        else if (first->block_side==ESide::INVALID) return ESide::INVALID;
        else if (ret==ESide::END) ret = first->block_side;
        else return ESide::INVALID;
    return ret;
}


void OptimizeManhattanPath(contour::EdgeVector<EdgeWithSide> &path, const Contour &bbs) {
    for (size_t i = 0; i+1<path.size(); i++) {
        /* If three edges
         * - form a U shape
         * - all are marked with either RIGHT or LEFT (any can also be END)
         * - either they are clockwise and marked LEFT or counterclockwise and marked RIGHT or all marked with END
         * Then we shorten them, eliminating the shorter of the first or third
         * After the elimination the path they are part of remain connected.*/
        EdgeWithSide &e1 = path[i], &e2 = path[i+1];
        const bool horizontal = is_horizontal(e2);
        const bool e1_grows = e1.GetStart()[horizontal] < e1.GetEnd()[horizontal];
        if (i+2<path.size()) {
            EdgeWithSide &e3 = path[i+2];
            const bool e3_grows = e3.GetStart()[horizontal] < e3.GetEnd()[horizontal];
            if (e1_grows!=e3_grows) { //The 3 edges are U-shaped
                const ESide side3 = IsUniformSide(path.begin()+i, 3);
                if (side3 == ESide::INVALID) continue; //The 3 edges are U-shaped, but go around bbs - don't try shortening
                if (side3 != ESide::END) {
                    //not all edges are from the original path, check our side
                    const bool e2_grows = e2.GetStart()[!horizontal] < e2.GetEnd()[!horizontal]; //other dimension (X or Y) than e1_grows and e3_groews
                    const bool clockwise = (e2_grows == e3_grows) == horizontal;
                    if (clockwise==(side3==ESide::RIGHT)) continue;
                }
                const bool e1_longer_than_e3 = fabs(e1.GetEnd()[horizontal]-e1.GetStart()[horizontal]) > fabs(e3.GetEnd()[horizontal]-e3.GetStart()[horizontal]);
                XY P, Q;
                if (e1_longer_than_e3) {
                    P[horizontal] = e3.GetEnd()[horizontal]; // cppcheck-suppress uninitvar
                    P[!horizontal] = e1.GetStart()[!horizontal];
                    Q = e3.GetEnd();
                } else {
                    P[horizontal] = e1.GetStart()[horizontal];
                    P[!horizontal] = e3.GetEnd()[!horizontal];
                    Q = e1.GetStart();
                }
                const double e2_relevant_coord = e2.GetStart()[horizontal];
                //now P is the point between the 2 edges that (should) replace e1->e2->e3
                //Q is the other end of the new edge.
                // We do a cycle here to cater for all kinds of bbs, see the example below.
                //      +--+    . . . . . .
                //      |  |    .         .           Here the dense dotted line represents the original
                //      |  +----.--------+.     -->A  path (well, three edges of it, which forms a
                //      |       .        |.           U-shape around bbs (solid line). The double line is the
                //      +---+   .        |.           shortened U-shape, changing it to more like an
                //           \  .        |.           L-shape, P and Q marks the two coordinates.
                // .......    \ .        |.           note that horizontal is true is this example.
                //       :     \         |.           Same as e1_longer_than_e3.
                //       P======*========*===Q.....
                //       :      .\       |.  :        Below we test if P->Q crosses bbs, which is a
                //       :      . \      |.  :        combination of four SimpleContours c1-c4 here.
                //       :    +-+  \    c1.  :        (Which may have holes, but for simplicity we
                //       :    | .\  \    |.  :        dont show them.) We identify which SimpleContour
                //       e1   | . \  \   |.  e3       we cross (c1, the cps are marked with two *s) and
                //       :    | .  \  \  |.  :        take the bounding box of its segment between the
                //       :    | .  c2  \ |.  :        two crosspoints. In this horizontal case, we define
                //       :    | .    \  \|.  :        a large Block between the X coordinates of the two
                //       :    | . . . \ . .  :  -->B  crosspoints and the Y extent of c1 Y coordinate
                //       :    |        \     :        (shown as sparse dotted rectangle).
                //       :    +---------+    :        The Y extent of the bounding box will be A and B on
                //       :                   :        the figure. Of these B falls in between the original
                //       :    +---+          :        e2 edge and PQ, so we take that. See this below.
                //       :    |c3 |          :
                //       :    +---+          :
                //       :                   :
                //       .......e2............
                //
                //             +----+
                //             | c4 |
                //             +----+

                //
                //      +--+
                //      |  |                          On this figure we have used 'B', to re-calculate P and Q.
                //      |  +-------------+            This will make us avoid c1, but not c2.
                //      |                |            (The reason we work with individual SimpleContours is that
                //      +---+            |            the bbs may have yet other components way below e2 (c4 in
                //           \           |            this example) and we want to ignore that.)
                // .......    \          |
                //       :     \         |            So we need a loop that iterates and finds a P-Q edge that
                //       :      \        |   ......   does not cross bbs. Note that we have a c3 in between
                //       :       \       |   :        e2 and c2, but a horizontal line can pass between c2 and c3
                //       :        \      |   :        (marked with <--C) that is OK as a simplification of the
                //       :    +-+  \    c1   :        path.
                //       :    |  \  \    |   :
                //       e1   |   \  \   |   e3
                //       :    |    \  \  |   :        Now, interested reader see the code rendition of this
                //       :    |    c2  \ |   :        algorithm by me. You can start bashing.
                //       :    |      \  \|   :
                //       P===================Q
                //       :    |        \     :
                //       :    +---------+    :
                //       :                   :  <--C
                //       :    +---+          :
                //       :    |c3 |          :
                //       :    +---+          :
                //       :                   :
                //       .......e2............
                //
                //             +----+
                //             | c4 |
                //             +----+
                while (!contour::test_equal(P[horizontal], e2_relevant_coord)) { // cppcheck-suppress legacyUninitvar
                    auto cps = bbs.CrossPoints(Edge(P, Q));
                    std::erase_if(cps, [](const contour::CPData &d) { return d.enters()!=contour::Tri::True && d.leaves()!=contour::Tri::True; });
                    if (cps.size()<2) {
                        //The shortened U did not cross bbs. It may touch
                        _ASSERT(cps.empty() || cps.front().type.rel != contour::CPRel::Cross);
                        break;
                    }
                    //the radically shortened U turn (when it becomes L turn) would cross
                    //the bbs. Find out, how much can we shorten the U.
                    //For this identify the SimpleContour, we bother.
                    std::sort(cps.begin(), cps.end(), [](auto &a, auto &b) {return a.other<b.other; });
                    const SimpleContour *c = [&cps]()->const SimpleContour * {
                        auto i = cps.begin();
                        auto j = cps.rbegin();
                        do if (i->contour_me->GetClockWise())
                            do if (i->contour_me==j->contour_me)
                                return i->contour_me;
                        while ((j++).base()!=i && j->xy==cps.back().xy);
                        while ((++i)!=cps.end());
                        return nullptr;
                    }();
                    if (c==nullptr) break; //we have not found a matching SimpleContour among the first and last CPs.
                    const Block &cbb = c->GetBoundingBox();
                    const Block cut_with = horizontal
                        ? Block(Range(std::min(P.x, Q.x), std::max(P.x, Q.x)), cbb.y.CreateExpanded(1))
                        : Block(cbb.x.CreateExpanded(1), Range(std::min(P.y, Q.y), std::max(P.y, Q.y)));
                    const Contour cut = Contour(*c) * cut_with; //TODO: This is now an unnecessary copy of '*c'.
                    const Range r = horizontal ? cut.GetBoundingBox().y : cut.GetBoundingBox().x;
                    //OK. Assuming horizontal, r now contains the Y extent of c in between P & Q.
                    //Looking at the above comment example, r contains A and B.
                    //Note both A and B must be on the same side of e2 as P->Q is. If not, we give up.
                    //We take the one closer to e2 (to make sure we finish the cycle in finite number
                    //of steps.
                    if (r.IsWithinBool(e2_relevant_coord)) break; //probably one of 'A' and 'B' equal to e2 -> no shortening possible.
                    //Now we know that both 'A' and 'B' (r.from and r.till) are on the same side of e2
                    if ((r.from<e2_relevant_coord)!=(P[horizontal]<e2_relevant_coord)) {
                        _ASSERT(0);
                        break; //P is on a different side of e2 than 'A' and 'B' - should not happen
                    }
                    P[horizontal] = Q[horizontal] = fabs(r.from-e2_relevant_coord) < fabs(r.till-e2_relevant_coord) ? r.from : r.till;
                }
                if (contour::test_equal(P[horizontal], e2_relevant_coord)) //no improvement found
                    continue;
                e2.block_side = ESide::END;
                const bool e3_disappears = P[horizontal]==e3.GetEnd()[horizontal];
                const bool e1_disappears = P[horizontal]==e1.GetStart()[horizontal];
                if (e3_disappears && e1_disappears) {
                    //This is the case of a perfect U-shape with equal length sides
                    //
                    //  -->-+...new.e2...+------ The edge before e1 and after e3
                    //      |            |       can be joined with the new e2
                    //      e1           e3
                    //      |            |
                    //      +-----e2-----+
                    e2.SetStartOnly(e1.GetStart());
                    e2.SetEndOnly(e3.GetEnd());
                    path.erase(path.begin()+i+2);
                    path.erase(path.begin()+i);
                    if (path.size()>i+1 && path[i].Edge::CheckAndCombine(path[i+1])) {
                        path[i].block_side = path[i+1].block_side;
                        path.erase(path.begin()+i+1);
                    }
                    //merge the edge before (the now deleted e1) with the combined (new_e2 + after_e3)
                    //only if they are not of opposite sides (one LEFT the other RIGHT)
                    if (i>0 && (path[i].block_side==ESide::END || 
                                 (i+1<path.size() && (path[i+1].block_side==ESide::END || path[i].block_side==path[i+1].block_side)))
                            && path[i - 1].Edge::CheckAndCombine(path[i])) {
                        path.erase(path.begin()+i);
                        i = std::max(size_t(1), i)-2;
                    } else
                        --i; //recheck
                } else if (e3_disappears) {
                    //This is the case where the e3 edge is shorter
                    //  -->-+
                    //      P...new.e2...+------ The edge after e3
                    //      |            |       can be joined with the new e2
                    //      e1           e3
                    //      |            |
                    //      +-----e2-----+
                    e1.SetEndOnly(P);
                    e2.SetStartOnly(P);
                    e2.SetEndOnly(e3.GetEnd());
                    path.erase(path.begin()+i+2);
                    //Try merging the resulting new e2 with the edge beyond the old e3
                    if (path.size()>i+2 && path[i+1].Edge::CheckAndCombine(path[i+2])) {
                        path[i+1].block_side = path[i+2].block_side;
                        path.erase(path.begin()+i+2);
                    }
                    --i; //recheck us again
                } else if (e1_disappears) {
                    //This is the case where the e1 edge is shorter
                    //                   +------
                    //  -->-+...new.e2...P       The edge before e1
                    //      |            |       can be joined with the new e2
                    //      e1           e3
                    //      |            |
                    //      +-----e2-----+
                    e2.SetStartOnly(e1.GetStart());
                    e2.SetEndOnly(P);
                    e3.SetStartOnly(P);
                    path.erase(path.begin()+i);
                    //try merging the old e2 with the edge before the old e1
                    if (i>0 && path[i-1].Edge::CheckAndCombine(path[i])) {
                        path.erase(path.begin()+i);
                        i = std::max(size_t(1), i)-2;
                    } else
                        --i; //recheck the edge now appeared at position 'i'
                } else {
                    //We keep the U shape, but tighter. Order P & Q
                    if (P[!horizontal]!=e1.GetEnd()[!horizontal])
                        std::swap(P[!horizontal], Q[!horizontal]);
                    _ASSERT(P[!horizontal]==e1.GetEnd()[!horizontal]);
                    _ASSERT(Q[!horizontal]==e3.GetStart()[!horizontal]);
                    e1.SetEndOnly(P);
                    e2.SetStartOnly(P);
                    e2.SetEndOnly(Q);
                    e3.SetStartOnly(Q);
                }
                continue; //We are done tightening a U-shape. Go to next edge.
            }
        }
        //we are not U-shaped, try to see if flipping the first two edges help.
        const ESide side2 = IsUniformSide(path.begin()+i, 2);
        if (side2 == ESide::INVALID) continue; //The 2 edges have bbs one on each side - we cant flip
        if (side2 != ESide::END) {
            //not all edges are from the original path, check our side
            const bool e2_grows = e2.GetStart()[!horizontal] < e2.GetEnd()[!horizontal]; //other dimension (X or Y) than e1_grows and e3_grows
            const bool clockwise = (e2_grows == e1_grows) != horizontal;
            if (clockwise==(side2==ESide::RIGHT)) continue;
        }
        const XY P = horizontal ? XY(e2.GetEnd().x, e1.GetStart().y) : XY(e1.GetStart().x, e2.GetEnd().y);
        const Edge new2[2] = {Edge{e1.GetStart(), P}, Edge{P, e2.GetEnd()}};
        const Block bb_new2(e1.GetStart(), e2.GetEnd());
        const auto cps = bbs.CrossPoints(std::begin(new2), std::end(new2), false, &bb_new2, contour::CalcRelation);
        if (std::ranges::none_of(cps, [](const contour::CPData &d) {return d.enters()==contour::Tri::True; })) {
            e1.SetEndOnly(P);
            e2.SetStartOnly(P);
            e1.block_side = e2.block_side = ESide::END;
            if (i>0 && path[i-1].Edge::CheckAndCombine(e1)) {
                path.erase(path.begin()+i);
                i--;
            }
            if (path.size()>i+2 && path[i+1].Edge::CheckAndCombine(path[i+2])) {
                path[i+1].block_side = path[i+2].block_side;
                path.erase(path.begin()+i+2);
                i = std::max(size_t(1), i)-2;
            }
        }
    }
    std::ranges::for_each(path, [](EdgeWithSide &e) { e.block_side = ESide::END; });
    path.Simplify(); //EdgeWithSide only merges edges with the same 'block_side'
}

std::list<PathCandidate>
LayoutSegmentManhattan(std::span<const XY> vias, const Contour &bbs, double /*arrow_ambiguous_percent*/,
                       const ProgressBase *progress) {
    std::list<PathCandidate> ret;

    //For each segment of vias, we can add two kind of Manhattan routing.
    //If the segment is horizontal or vertical, we can just add one.
    //And we need the Descartes product of all these variants.
    //So here we step through a binary number, whose digits tell us
    //which of the two variants to add. If the segment in via is either
    //horizontal or vertical we do not add anything for the binary digit one.
    //(second==true).
    std::vector<XY> raw_path;
    raw_path.reserve(vias.size()*2);
    for (size_t k = 0; k<size_t(1)<<(vias.size()-1); k++) {
        raw_path.clear(); //keeps reserved memory
        raw_path.reserve(vias.size()*2);
        for (size_t j = 0; j<vias.size()-1; j++) {
            bool second = k & (size_t(1)<<j);
            raw_path.push_back(vias[j]);
            if (vias[j].x==vias[j+1].x || vias[j].y==vias[j+1].y) {
                if (!second) continue;
                raw_path.clear();
                break;
            }
            if (second) raw_path.emplace_back(vias[j].x, vias[j+1].y);
            else raw_path.emplace_back(vias[j+1].x, vias[j].y);
        }
        if (raw_path.empty()) continue;
        raw_path.push_back(vias[vias.size()-1]);

        //Now find all crosspoints of the raw path with the forbidden area and replace segments crossing
        //with Manhattan workarounds. Every time we will have two alternative replacements.
        //These half-baked paths are stored in stack.

        struct Candidate {
            /* Contains the current candidate path.
             * Edges marked with 'END' are from the original raw_path.
             * Edges marked with 'RIGHT' and 'LEFT' go around a simplecontour in
             * clockwise or counterclockwise dir, respectively. */
            contour::EdgeVector<EdgeWithSide> path;
            unsigned attempts = 0;
        };

        std::vector<Candidate> stack;
        stack.emplace_back(contour::EdgeVector<EdgeWithSide>{raw_path});
        std::ranges::for_each(stack.back().path, [](EdgeWithSide &e) { e.block_side = ESide::END; });

        while (stack.size()) {
            progress->Busy();
            auto &c = stack.back();
            size_t from = std::ranges::find_if(c.path, [](const Edge &e) { return e.IsVisible(); }) - c.path.begin();
            std::vector<contour::CPData> cps = bbs.CrossPoints(c.path.begin()+from, c.path.end(), false, nullptr, contour::CalcRelation);
            if (cps.empty()) {
            candidate_ready:
                // try to make edges shorter
                // If we have 3 consecutive edges
                // - in a U shape
                // - all being either (END or LEFT) or (END or RIGHT)
                // we will shorten them & re-submit
                constexpr Edge::Update U(contour::EBinaryValueUpdate::SET, contour::EBinaryValueUpdate::CLEAR); //clear marks and set visibility
                c.path.Apply(U);
                c.path.Simplify();
                OptimizeManhattanPath(c.path, bbs);
                //move it to the list of candidates
                ret.emplace_back(std::move(c.path));
                stack.pop_back();
                continue;
            }
            if (c.attempts++>200) {
                //A safety thing. We may end up in situation when the above algorithm
                //gets into an infinite loop. Terminate that branch of the search tree.
                stack.pop_back();
                continue;
            }
            std::sort(cps.begin(), cps.end(), [](auto &a, auto &b) {return a.other<b.other; });
            for (auto cp = cps.begin(); cp!=cps.end(); /*nope*/) {
                //Find the first CP that *enters* bbs.
                cp = std::find_if(cp, cps.end(), [](const contour::CPData &d) { return d.enters()==contour::Tri::True; });
                //Find the next CP that reaches the contour of this very same SimpleContour
                //Touches are not enough. We assume the end of c.path is definitely outside
                //bbs, so endpoints are not considered.
                const auto cp2 = std::find_if(cp, cps.end(), [cp](const contour::CPData &d)
                                              { return cp->contour_me==d.contour_me && d.leaves()==contour::Tri::True; });
                if (cp2==cps.end()) {
                    //_ASSERT(cp==cps.end()); It may happen that we have an 'enter' cp not followed by a 'leave'
                    //if we start or end on bbs. No biggie.
                    //here we see that c.path has no more crosspoints with bbs
                    goto candidate_ready;
                } else {
                    //We have found the entry and exit point of c.path through bbs
                    //Check how the edge that enters 'bbs' is marked.
                    const size_t edge = from + (cp->other.pos==1 ? cp->other.edge+1 : cp->other.edge);
                    const ESide side = c.path[edge].block_side;
                    const auto alternatives = ManhattanRound(*cp->contour_me, cp->xy, cp2->xy,
                                                             /* clockwise=*/ side==ESide::END || side==ESide::RIGHT,
                                                             /*cclockwise=*/ side==ESide::END || side==ESide::LEFT);
                    Candidate C[2];
                    if (alternatives[0].size() && alternatives[1].size())
                        C[0] = c, C[1] = std::move(c);
                    else if (alternatives[0].size())
                        C[0] = std::move(c);
                    else if (alternatives[1].size())
                        C[1] = std::move(c);
                    else {
                        //No alternative needed, just go to the next cp of the path
                        //_ASSERT(0); //If the start/endpoint is outside the block, we may get this.
                        cp = cp2;
                        if (cp!=cps.end()) ++cp;
                        continue; //the for cycle over the crosspoints
                    }
                    _ASSERT(c.path.empty());
                    for (unsigned u = 0; u<2; u++) {
                        if (alternatives[u].empty()) continue;
                        auto ii = Replace(C[u].path, C[u].path.begin()+from+cp->other.edge, cp->other.pos, C[u].path.begin()+from+cp2->other.edge, cp2->other.pos,
                                            alternatives[u], alternatives[u].begin(), 0., --alternatives[u].end(), 1.);
                        _ASSERT(ii);
                        std::for_each(C[u].path.begin(), ii->first, [](Edge &e) { e.SetVisible(false); });
                        const ESide new_side = u ? ESide::LEFT : ESide::RIGHT;
                        std::for_each(ii->first, ii->second, [new_side](EdgeWithSide &e) { e.block_side = new_side; });
                        if (ii->first != C[u].path.begin()) {
                            auto last_orig = std::prev(ii->first);
                            const XY old_delta = last_orig->GetEnd() - last_orig->GetStart();
                            if (last_orig->Edge::CheckAndCombine(*ii->first, 0)) { //use that of Edge:: to ignore 'block_side'
                                const XY new_delta = last_orig->GetEnd() - last_orig->GetStart();
                                //If the last edge (before we have inserted) has disappeared, mark it to be
                                //checked for overlaps again.
                                if (old_delta.DotProduct(new_delta)<0) {
                                    last_orig->SetVisible();
                                    last_orig->block_side = ii->first->block_side;
                                }
                                C[u].path.erase(ii->first);
                            }
                        }
                        C[u].path.Simplify();
                        C[u].path.RemoveLoops();
                        _ASSERT(C[u].path.IsConnected());
                        if (c.path.empty()) c = C[u];
                        else stack.push_back(std::move(C[u]));
                    }
                } //if we found & replaced a segment of c.path that crossed bbs
                break;
            } //for cycle over the crosspoints
            if (stack.back().path.empty())
                stack.pop_back();
        } //while to process all elements on the stack
    } //for to process all variants of manhattan edges between the vias
    return ret;
}



/** Creates a list of arrow layout candidates.
 * @param [in] vias The waypoints we need to touch en-route. It shall have at least 2 elements:
 *             the start and end.
 * @param [in] bbs The combined area, that we should avoid when laying out the paths.
 * @param [in] routing The style of the paths to create.
 * @param [in] arrow_ambiguous_percent How much going around a block may be longer than
 *             the other way to skip considering the alternative. See BlockChart::arrow_ambiguous_percent.
 * @param progress We can report progress here, or at least check if user abort requested.
 * @returns A list of path candidates. */
std::list<PathCandidate>
LayoutSegment(std::span<const XY> vias, const Contour &bbs, EArrowRouting routing, double arrow_ambigous_percent,
              const ProgressBase *progress)
{
    if (routing == EArrowRouting::Manhattan)
        return LayoutSegmentManhattan(vias, bbs, arrow_ambigous_percent, progress);

    struct Backtrack
    {
        contour::EdgeList<EdgeWithSide> path;   //contains the current candidate path
        contour::EdgeList<EdgeWithSide>::iterator i; //contains the position where we are
        std::vector<contour::CPData> r;  //contains the crosspoints of edge 'i' with the blocks we want to avoid
        size_t cp_index;                        //in a saved state, tells us which crosspoint did we turn left or right
        bool used_front;                        //in a saved state, tells us if turned left or right at cp_index
        std::vector<XY> points;                 //contains the vertices of the 'path' before the current processing run through 'path'
        unsigned remaining_attempts;            //The number of remaining steps when the process was forked
    };

    constexpr unsigned max_attempts = 10; //This is where we set the remaining attempts

    std::list<PathCandidate> ret;

    std::list<Backtrack> stack(1);
    stack.back().path.append(vias);
    for (auto &e : stack.back().path)
        e.block_side = ESide::END; //END means the original edge
    stack.back().i = ++stack.back().path.begin();
    stack.back().remaining_attempts = max_attempts;

    bool resuming = false;

    if (routing == EArrowRouting::Straight) {
        //if we do not route around blocks, use this result already.
        Path tmp;
        tmp.append(stack.back().path.begin(), stack.back().path.end());
        tmp.Simplify();
        ret.push_back(std::move(tmp));
        return ret;
    }

    while (stack.size()) {
        progress->Busy();
        auto c = --stack.end();
        //Limit the number of refinement steps
        while (c->remaining_attempts--) {
            //First try to avoid all blocks in the way of path.
            //We know these blocks do not intersect
            if (!resuming) {
                c->i = c->path.begin();
                //save the current vertex list to later check if we have changed
                c->points = contour::GetPoints(c->path.begin(), c->path.end());
            }
            bool did_we_change_c_path = false;
            //For each edge, check if it goes through forbidden areas
            //Since we start with waypoints that are outside the forbidden areas,
            //any edge between them that crosses a forbidden areas, will cross it fully.
            //After we had replaced the edge with a way around the forbidden area, it will
            for (; c->i!=c->path.end(); /* nope */) {
                if (c->i->GetStart()==c->i->GetEnd()) {
                    //nothing to do
                    c->i++;
                    continue;
                }
                if (!c->i->check_for_overlap) {
                    //we have added these edges as ones following the contour
                    //of bbs. No need to re-check them again
                    c->i++;
                    continue;
                }
                const EdgeWithSide edge(*(c->i));
                decltype(c->r)::iterator cp;
                if (!resuming) {
                    c->r = bbs.CrossPoints(edge);
                    //sort them along the edge 'edge'
                    std::ranges::sort(c->r, {}, [](auto &a) {return a.other.pos; });
                    cp = c->r.begin();
                } else {
                    cp = c->r.begin() + c->cp_index;
                }
                for (/*nope*/; cp!=c->r.cend(); /*nope*/) {
                    //find the last cp with this contour
                    auto cp2 = std::ranges::find(std::make_reverse_iterator(c->r.end()),
                                                 std::make_reverse_iterator(std::next(cp)),
                                                 cp->contour_me, &contour::CPData::contour_me);
                    if (cp2.base()==cp) {//we were the (unpaired) last cp for this contour. Probably a touch. Skip it.
                        cp++;
                        continue;
                    }
                    if (cp->other.pos == cp2->other.pos) {
                        //if the two points are the same, skip.
                        cp = cp2.base();
                        continue;
                    }
                    //If the edge is fully replaced by a part of bbs contour,
                    //that must mean we have already replaced this part /...
                    //of the original single c->path edge.             /:
                    //It happens for concave parts of bb: like        /.:
                    //(dots are the bb contour, / is the edge in 'c->i'.
                    //In this case we do not have to follow the contour as we
                    //have already done so - but straightened.
                    if ((edge.GetStart()==cp->xy  && edge.GetEnd()==cp2->xy) ||
                        (edge.GetStart()==cp2->xy && edge.GetEnd()==cp->xy))
                        //Now this may also happen, if the original input edge accidentally
                        //falls on the contour. This may happen at a->a arrows. In that case we replace
                        if (edge.block_side!=ESide::END) {
                            cp = cp2.base();
                            continue;
                        }
                    //If we have branched on this point before, abandon this track
                    for (auto ss = ++stack.rbegin(); !(ss==stack.rend()); ++ss)
                        for (auto &crpt : ss->r)
                            if (crpt.xy.test_equal(cp->xy))
                                goto done_with_path;

                    std::vector<Path> pair = cp->contour_me->SplitAt({cp->me, cp2->me});
                    if (pair.front().size() && pair.back().size()) {
                        const bool use_front = [&]() {
                            if (resuming) {
                                resuming = false;
                                return !c->used_front;
                            }
                            const double d1 = pair.front().GetLength();
                            const double d2 = pair.back().GetLength();
                            bool use_front = d1<d2;
                            ////if the shorter path fully overlaps c->i, we ignore this case by deleting 'pair'
                            //auto &p = use_front ? pair.front() : pair.back();
                            //bool p_is_along_c_i = true;
                            //for (auto ii=p.begin(); ii!=p.end() && p_is_along_c_i; ++ii)
                            //    ii->Crossing(*c->i, false, &p_is_along_c_i);
                            //if (p_is_along_c_i) {
                            //    //Ah, c->i runs along the edges of bbs between cp & cp2 - ignore this cp pair
                            //    pair.front().clear();
                            //    pair.back().clear();
                            //    return use_front;
                            //}
                            const double ratio = (1+arrow_ambigous_percent/100);
                            if (ratio>d1/d2 && ratio>d2/d1) {
                                //OK, we will come back and check this the non-selected route later
                                c->cp_index = cp - c->r.cbegin();
                                c->used_front = use_front;
                                stack.emplace_back();
                                std::tie(stack.back().path, stack.back().i) = CopyList(c->path, c->i);
                                stack.back().r = c->r;
                                stack.back().remaining_attempts = c->remaining_attempts;
                                //now move cp from c->r to stack.back().r
                                cp = stack.back().r.begin() + c->cp_index;
                                cp2 = stack.back().r.rbegin() + (cp2 - c->r.rbegin());
                                c++;
                            }
                            return use_front;
                        }();
                        //Note that cut returned c->r.first the crosspoint that is 'earlier in c->path.
                        //So the pair.front() is on the left side of c->path, thus,
                        //the block will be on the right side so left = !use_front
                        //Now this whole thing is inverted if we invert the path before insert
                        auto &p = use_front ? pair.front() : pair.back();
                        _ASSERT(p.IsConnected());
                        if (p.size()==0) {
                            cp = cp2.base();
                            continue;
                        }
                        bool on_left_side = p[0].GetStart().DistanceSqr(cp->xy) > p[0].GetStart().DistanceSqr(cp2->xy);
                        if (on_left_side)
                            p.Invert();
                        if (p.size()==1 && c->i->test_equal(p.front())) {
                            cp = cp2.base();
                            continue;
                        }
                        //Replace the part of c->i in between cp and cp2 to 'p'.
                        auto ii = Replace(c->path, c->i, cp->other.pos, c->i, cp2->other.pos,
                                          p, p.begin(), 0, std::prev(p.end()), 1);
                        _ASSERT(ii);
                        //Mark edges as ones being around the contour of a block being avoided
                        for (auto i = ii.value().first; i!=ii.value().second; i++) {
                            i->block_side = on_left_side ? ESide::LEFT : ESide::RIGHT;
                            i->check_for_overlap = false;
                        }
                        //Mark the edge immediately before and after as free (so that they can be part of complex hull with the inserted part)
                        if (ii.value().first != c->path.begin())
                            std::prev(ii.value().first)->block_side = ESide::INVALID;
                        if (ii.value().second!=c->path.end())
                            ii.value().second->block_side = ESide::INVALID;
                        c->i = ii.value().second; //c->i points to the one after the last edge inserted
                        //adjust the 'pos' values later in 'r', since we have cropped c->i
                        if (cp2->other.pos<1) {
                            for (auto i = cp2.base(); i!=c->r.end(); i++) {
                                _ASSERT(i->other.pos > cp2->other.pos);
                                i->other.pos = (i->other.pos - cp2->other.pos)/(1-cp2->other.pos);
                            }
                        } else
                            //If cp2->other.pos==1, we have entirely replaced the original c->i,
                            //and now c->i equals the next fresh edge we need to check after the original c->i
                            //and the c->i++ below would take us beyond that.
                            c->i--;
                        did_we_change_c_path = true;
                    }
                    cp = cp2.base(); //actually one element after *cp2
                }
                c->i++;
            }
            if (!did_we_change_c_path)
                break;
            _ASSERT(IsConnected(c->path.begin(), c->path.end()));
            //Now try to shorten edges that curve concave
            for (auto i = c->path.begin(); i!=c->path.end(); /* nope */) {
                _ASSERT(IsConnected(c->path.begin(), c->path.end()));
                ESide side = i->block_side; //May be INVALID == free
                                            //walk till an edge with opposite side
                auto j = std::next(i);
                for (; j!=c->path.end(); j++) {
                    if (j->block_side==ESide::INVALID || j->block_side==ESide::END) continue;
                    else if (Opposite(j->block_side)==side) break;
                    else side = j->block_side; //here 'side' may be invalid, so we set to that of j
                }
                if (side==ESide::INVALID || side==ESide::END) //All edges are free - no complex hull is needed.
                    break;
                //now j is the after the last edge with the same side as 'i'. j may be end().
                //take cps of i->start -> std::prev(j)->end
                //see which side the curve is of this line is
                std::list<EdgeWithSide> tmp = contour::ConvexHull<std::list<EdgeWithSide>>(side!=ESide::LEFT, i, j);
                _ASSERT(IsConnected(tmp.begin(), tmp.end()));
                //if the result is two free edges, insert a dot in the middle to keep that point fixed.
                if (tmp.size()==2 && tmp.front().block_side==ESide::INVALID &&
                    tmp.back().block_side==ESide::INVALID) {
                    auto ins = tmp.insert(--tmp.end(), {tmp.front().GetEnd(), tmp.front().GetEnd()});
                    ins->block_side = side;
                    ins->check_for_overlap = false;
                } else if (tmp.size()>2) {
                    for (auto ii = ++tmp.begin(); ii!=--tmp.end(); ii++) {
                        ii->block_side = side;
                    }
                }
                for (auto tmp_i = i, tmp2_i = tmp.begin(); tmp_i!=j && tmp2_i != tmp.end(); tmp_i++, tmp2_i++)
                    if ((const Edge&)*tmp_i != (const Edge&)*tmp2_i) {
                        c->path.erase(i, j);
                        //Ensure tight fit (connectedness) between the inserted part and before/after
                        if (tmp.size() && c->path.size()) {
                            if (j!=c->path.end() && j!=c->path.begin())
                                std::prev(j)->SetEndOnly(tmp.front().GetStart());
                            else if (j==c->path.end())
                                c->path.back().SetEndOnly(tmp.front().GetStart());
                            if (j!=c->path.end())
                                j->SetStartOnly(tmp.back().GetEnd());
                        }
                        c->path.splice(j, std::move(tmp));
                        break;
                    }
                //here either tmp was the same as [i,j) or we have swapped the two.
                if (std::prev(j)->block_side==ESide::INVALID || std::prev(j)->block_side==ESide::END)
                    i = std::prev(j);
                else
                    i = j;
            }
            //If our vertices are exactly the same as when we have started to process all
            //edges in c->path, we exit, as we are likely in an infinite loop.
            if (c->points == contour::GetPoints(c->path.begin(), c->path.end()))
                break;
        }
        //If we have generated a path, store it among the resulting paths.
        c->path.Simplify();
        if (c->path.size())
            ret.emplace_back(std::move(c->path));
    done_with_path:
        stack.pop_back();
        resuming = true;
    }
    return ret;
}

}//namespace

/** This is called after the path have been finalized.
 * We clip it and arrange labels.*/
void Arrow::PathFound(Canvas &canvas)
{
    path_visible = path;
    bool changed = false;
    bool visible_changed = false;

    //prepare for clip
    clip_ah.clear();
    clip_block.clear();

    //Chop the path to the two blocks if there is an arrowhead
    //We need to do the 'end' of the arrow first, so that when we trim path or path_visible
    //differently, their end remains the same for the second pass.
    //Below we do linear extend if we were asked to (in a previous call to PathFound - this happens
    //at de-overlapping). We also set 'linearextend' for a potential future call of PathFound().
    for (int e = 1; e>=0; e--) {
        if (ends[e].is_block) {
            auto b = chart.GetBlockByNameWithError(ends[e].pos[0].blocks.front().name,
                                                   ends[e].pos[0].blocks.front().file_pos.start,
                                                   parent, "arrow");
            if (!b) { valid = false; return; }
            const auto ah = e ? style.arrow.read().endArrowHead : style.arrow.read().startArrowHead;
            //clip only if the endpoint is inside the block and the block has area
            const XY p = e ? path.back().GetEnd() : path.front().GetStart();
            const Contour &area_to_use = b->GetVisibleCover();
            XY on_edge; 
            const double distance = area_to_use.Distance(p, on_edge);
            constexpr double inside_tolerance = 0.1;
            if (inside_tolerance < distance) {
                //If we are not inside and no need to linearly extend, do nothing
                if (!linearextend[e]) continue;
                //Try linearly extend to hit 'area_to_use'
                const XY tangent = e ? path.back().NextTangentPoint(1) : path.front().PrevTangentPoint(0);
                Range r = area_to_use.Cut(p, tangent);
                if (r.IsInvalid())
                    continue; //We don't cross 'area_to_use' Nothing to do.
                const XY pp = (1-r.MidPoint())*p + r.MidPoint()*tangent;
                if (e) {
                    path.append(pp);
                    path_visible.append(pp);
                } else {
                    path.prepend(pp);
                    path_visible.prepend(pp);
                }
                changed = visible_changed = true;
            }
            linearextend[e] = style.route_linearextend.value_or(true);
            //Now do the chopping part.
            if (!ends[e].clip_block)
                continue;
            auto cps = path.CrossPoints(false, nullptr, area_to_use);
            if (!cps.size())
                continue;

            PathPos trim;
            if (ends[1-e].is_block && ends[e].pos[0].blocks.front().name==ends[1-e].pos[0].blocks.front().name) {
                //If both ends are blocks and the two blocks are the same, we need special handling.
                //If we just have 2 cps, that is OK. Else we do not know when do the arrow actually leave
                //or enter the shape, so we just trim at the first or last cp.
                if (e==0) trim = cps.front().me;
                else trim = cps.back().me;
            } else {
                if (e==0)
                    trim = std::max_element(cps.begin(), cps.end(),
                                            [](const auto &a, const auto &b) {return a.me<b.me; })->me;
                else
                    trim = std::min_element(cps.begin(), cps.end(),
                                            [](const auto &a, const auto &b) {return a.me<b.me; })->me;
            }
            //Now trim the visible path (for labels being positioned correctly)
            if (e==0)
                path_visible.SetStart(trim);
            else
                path_visible.SetEnd(trim);
            visible_changed = true;

            //If the user specified 'distance_pixel' or 'distance_percent',
            //we need to trim to the contour (and more). We also need to trim
            //to contour if there is an arrowhead.
            if (ends[e].distance_percent || ends[e].distance_pixel || !ah.IsNone()) {
                if (e==0)
                    path.SetStart(trim);
                else
                    path.SetEnd(trim);
                changed = true;
            } else
                clip_block += area_to_use; //Clip if no distance and no arrowhead.
        } else {
            linearextend[e] = false;
        }
    }
    for (unsigned e = 0; e<2; e++)
        if (ends[e].distance_percent || ends[e].distance_pixel) {
            const double dist = ends[e].distance_pixel +
                (ends[e].distance_percent ? ends[e].distance_percent/100*path.GetLength() : 0);
            if (dist<-THRESHOLD) {
                if (e==0) {
                    path.TruncateStart(-dist);
                    path_visible.TruncateStart(-dist);
                } else {
                    path.TruncateEnd(-dist);
                    path_visible.TruncateEnd(-dist);
                }
                changed = visible_changed = true;
            } else if (dist>THRESHOLD) {
                path.LinearExtend(dist, e==1, true);
                path_visible.LinearExtend(dist, e, true);
                changed = visible_changed = true;
            }
        }
    _ASSERT(path.IsConnected());
    if (changed) path.Simplify(THRESHOLD);
    if (visible_changed) path_visible.Simplify(THRESHOLD);
    _ASSERT(path.IsConnected());
    if (path.size()) {
        area = path.SimpleWiden(style.line.LineWidth());
        clip_ah += style.arrow.read().ClipForLine(path, style.line, true);
        area -= clip_ah;
        area -= clip_block;
        area += style.arrow.read().Cover(path, style.line);
    } else
        area.clear();
    area.arc = this;

    //lay out labels
    if (path_visible.size() && labels.size()) {
        const double len = path_visible.GetLength(); //re-calculate due to trimmings
        _ASSERT(len>0);
        for (auto &l:labels)
            clip_block += l->LayoutFinalize(canvas, style.line, path_visible, len);
    }
    chart.Progress.DoneBulk(ESections::LAYOUT_ARROWS);
    redo = false;
}

/** Returns the area our arrow blocks when laying out other arrows.
 * It is the path of the arrow extended with the max of our route_arrow_distance
 * value (plus half the line width) and the other_arrow_distance parameter.
 * If we are not supposed to block other arrows (route.block_others is false),
 * we return the empty Contour.*/
Contour Arrow::GetMyBlockAreaForDeoverlap(double other_arrow_distance, bool is_end) const {
    //If we block nobody, return empty
    if (!style.route_block_others.value_or(false)) return {};
    //If we are merged into another arrow, ignore me
    if (IsMergedToAnother()) return {};
    const bool use_end_segment = path_visible.empty();
    const Path& p = use_end_segment ? ends[is_end].dir_segment : path_visible;
    //If we have no end segment, nothing is blocking
    if (p.empty()) return {};
    const auto end_cap = use_end_segment ? contour::EExpandType::EXPAND_ROUND : contour::EExpandType::EXPAND_MITER;
    //substract 0.1 so that other arrows which has their end de-overlapped
    //by this amount do not accidentally fall to the edge of this block
    const double dist = std::max(GetMyBlockDistance(), other_arrow_distance) - 0.1;
    return p.SimpleWiden(dist, end_cap);
}


/** Lays out an arrow in horizontal or vertical dir.
 * 
 * @param [in] canvas The canvas where we are laying out the arrow.
 * @param [in] horizontal If +1, we lay out the arrow horizontally, -1: vertically, 0: either horizontally or vertically
 * @returns false if we could not do it and have fallen back to
 * 'straight'. */
bool Arrow::LayoutHorizVert(Canvas &canvas, int horizontal)
{
    //One of the ends must be a block with no port and compass point
    const std::array<bool, 2> solo = {
        !ends[0].from_coordinate && ends[0].port.name.length()==0 && ends[0].dir.number==-1,
        !ends[1].from_coordinate && ends[1].port.name.length()==0 && ends[1].dir.number==-1 };
    if (!solo[0] && !solo[1]) {
        chart.Error.Error(file_pos.start, "For horizontal or vertical routing one of the endpoints must be a block with no port and compass point specified. Using straight routing.");
        style.routing = EArrowRouting::Straight;
        return false;
    } else {
        if (solo[0] && solo[1]) {
            //if both are just solo blocks, pick the center of their intersection in the relevant dir
            auto b0 = chart.GetBlockByName(ends[0].pos[0].blocks.front().name, {}, true, true); //no prefix needed, by now these must be resolved unique names
            auto b1 = chart.GetBlockByName(ends[1].pos[0].blocks.front().name, {}, true, true); //no prefix needed, by now these must be resolved unique names
            const Contour c0 = b0[0]->GetVisibleCover();
            const Contour c1 = b1[0]->GetVisibleCover();
            for (unsigned y = horizontal <= 0 ? 0 : 1U; y <= (horizontal >= 0 ? 1U : 0); y++) 
                if (const Range r0 = c0.GetBoundingBox()[y], r1 = c1.GetBoundingBox()[y]
                    ; r0.Overlaps(r1)) {
                    XY start = ends[0].offset_pixel + b0[0]->outer_line.Spans().Scale(ends[0].offset_percent / 100);
                    XY end = ends[1].offset_pixel + b1[0]->outer_line.Spans().Scale(ends[1].offset_percent / 100);
                    //Now we may have only one one of them offset in the relevant direction (or have two offsets equal)
                    if (start[y] != end[y]) {
                        //Take the nonzero or the smaller in absolute value
                        if (start[y] == 0) start[y] = end[y];
                        else if (end[y] == 0) end[y] = start[y];
                        else if (fabs(start[y]) < fabs(end[y])) end[y] = start[y];
                        else start[y] = end[y];
                    }
                    _ASSERT(start[y] == end[y]);
                    start[y] = end[y] += (r0 * r1).MidPoint();
                    //TODO: for irregular shape, get the horiz/vertical cut of c0 and c1 instead
                    start[!y] += c0.GetBoundingBox().Centroid()[!y];
                    end[!y] += c1.GetBoundingBox().Centroid()[!y];
                    path = { start,end };
                    goto path_found;
                }
            chart.Error.Error(file_pos.start, StrCat("Blocks '", b0[0]->name_full, "' and '",
                                                     b1[0]->name_full, "' don't overlap to allow ",
                                                     horizontal==1 ? "a horizontal" : horizontal==-1 ? "a vertical" : "either a horizontal or a vertical",
                                                     "arrow/line between them. Using straight routing."));
            style.routing = EArrowRouting::Straight;
            return false;
        } else {
            //only one end is a single block
            const bool index = solo[1];
            std::array<XY, 2> points;
            for (unsigned xy = 0; xy<2; xy++) {
                auto c = chart.GetCoord(ends[!index].pos[xy], xy, parent);
                if (!c) {
                    _ASSERT(0);
                    chart.Error.Error(ends[!index].file_pos, "Cant translate this to coordinates. Ignoring arrow.");
                    redo = valid = false;
                    chart.Progress.DoneBulk(ESections::LAYOUT_ARROWS);
                    return true;
                }
                points[!index][xy] = *c;
            }
            auto b = chart.GetBlockByName(ends[index].pos[0].blocks.front().name, {}, true, true); //no prefix needed, by now these must be resolved unique names
            const Contour c = b[0]->GetVisibleCover();
            for (unsigned y = horizontal <= 0 ? 0 : 1U; y <= (horizontal >= 0 ? 1U : 0); y++)
                if (const Range r = c.GetBoundingBox()[y]; r.IsWithinBool(points[!index][y])) {
                    const XY offset = ends[index].offset_pixel + b[0]->outer_line.Spans().Scale(ends[index].offset_percent/100);
                    points[index][y] = points[!index][y];
                    //TODO: for irregular shape, get the horiz/vertical cut of c instead
                    points[index][!y] = c.GetBoundingBox().Centroid()[!y];
                    points[index] += offset;
                    //What to do, when the arrow end at the block gets offset (e.g., due to de-overlap)??
                    //For now we also shift the coordinate-end, which is bad, because goes against what the user wanted.
                    if (offset[y])
                        points[!index][y] += offset[y];
                    path = { points[0], points[1] };
                    goto path_found;
                } 
            chart.Error.Error(ends[index].file_pos, StrCat("This block is not aligned with the other end to allow ",
                                                           horizontal == 1 ? "a horizontal" : horizontal == -1 ? "a vertical" : "either a horizontal or a vertical",
                                                           "arrow/line between them. Using straight routing."));
            style.routing = EArrowRouting::Straight;
            return false;
        }
    }
path_found:
    //Now fill in clip_block and de-overlap for each end
    //as they are filled in for other routing methods later.
    for (unsigned e=0; e<2;e++)
        if (ends[e].is_block) {
            //add percentage offset
            auto b = chart.GetBlockByName(ends[e].pos[0].blocks.front().name, {}, true, true); //no prefix needed, by now these must be resolved unique names
            const XY d = e ? path.back().GetEnd() : path.front().GetStart();
            //decide if we shall clip
            XY dummy;
            if (style.route_clip_block)
                ends[e].clip_block = *style.route_clip_block;
            else //if user has not set, we clip if specified as a block or are inside
                ends[e].clip_block = !ends[e].from_coordinate ||
                b[0]->GetVisibleCover().Distance(d, dummy)<0.01;
            //Decide if we shall de-overlap
            ends[e].deoverlap = style.route_arrow_distance.value_or(0)>0 &&
                (!ends[e].from_coordinate ||
                 b[0]->GetVisibleCover().Distance(d, dummy)<0.01);
        } else {
            ends[e].clip_block = ends[e].deoverlap = false;
        }
    PathFound(canvas);
    return true;
}


/** Lays out an arrow.
* Fills 'path' and 'path_visible'.
* 'block' contains an area to avoid. 
* @Returns the score of the best path found*/
PathScore Arrow::Layout(Canvas &canvas, Contour &&block)
{
    adjusted = { false, false };
    //If the arrow is between blocks that get hidden due to the same collapsed
    //ancestor, we don't show them.
    if (valid) {
        //Collect for each end and coordinate, which block causes them to be hidden (if at all)
        std::array<std::array<const BlockBlock*, 2>, 2> hidden_by{ { {nullptr, nullptr}, {nullptr, nullptr} } };
        for (unsigned e = 0; e<2; e++)
            if (ends[e].is_block) {
                //If the end is a block, both coordinates are hidden if that block is hidden.
                const BlockBlock* const b = chart.GetBlockByName(ends[e].pos[0].blocks.front().name, {}, true, true)[0];
                hidden_by[e][0] = b->hidden_by;
                hidden_by[e][1] = b->hidden_by;
            } else
                for (unsigned xy = 0; xy<2; xy++)
                    //If a coordinate of the end is a parent and the parent is hidden/collapsed we use that
                    if (ends[e].pos[xy].IsParent()) {
                        if (parent->hidden_by) hidden_by[e][xy] = parent->hidden_by;
                        else if (parent->collapsed.value_or(false)) hidden_by[e][xy] = parent;
                        else hidden_by[e][xy] = nullptr;
                    } else
                        //If all blocks mentioned are hidden by the same block, we use that
                        for (const StringWithPos& sp: ends[e].pos[xy].blocks)
                            if (const BlockBlock* const b = chart.GetBlockByName(sp.name, {}, true, true)[0]
                                ; !b->hidden_by) {
                                hidden_by[e][xy] = nullptr;
                                break;
                            } else if (!hidden_by[e][xy]) {
                                hidden_by[e][xy] = b->hidden_by;
                            } else if (hidden_by[e][xy]!=b->hidden_by) {
                                hidden_by[e][xy] = nullptr;
                                break;
                            }
        for (unsigned e = 0; e<2; e++)
            for (unsigned xy = 0; xy<2; xy++)
                if (hidden_by[e][xy]!=hidden_by[0][0])
                    hidden_by[0][0]=nullptr;
        if (hidden_by[0][0]) //All ends and coordinates are hidden due to the same collapsed block
            valid = false;
    }
    if (!valid) {
        redo = false;
        path.clear();
        chart.Progress.DoneBulk(ESections::LAYOUT_ARROWS);
        return {};
    }

    //Handle hidden blocks in start and end
    for (unsigned e = 0; e<2; e++)
        if (ends[e].is_block) {
            //Replace to the collapsed block causing the block to be hidden
            const BlockBlock * const b = chart.GetBlockByName(ends[e].pos[0].blocks.front().name, {}, true, true)[0];
            _ASSERT(ends[e].pos[0].blocks.front().name==ends[e].pos[1].blocks.front().name); //is_block means same name for both coords.
            if (b->hidden_by) {
                for (unsigned xy = 0; xy<2; xy++)
                    ends[e].pos[xy].blocks.front().name = b->hidden_by->name_unique;
                //If the collapsed block don't have this port, drop it.
                if (ends[e].port.name.size() && !b->hidden_by->GetPort(ends[e].port.name))
                    ends[e].port.name.clear();
            }
        } else
            for (unsigned xy = 0; xy<2; xy++)
                if (!ends[e].pos[xy].IsParent() && CheckExists(ends[e].pos[xy].blocks, "arrow", { HandleHidden::Remove })) { //do not check for 'parent' coords (just a number)
                    valid = false;
                    chart.Progress.DoneBulk(ESections::LAYOUT_ARROWS);
                    return {};
                }
    //Translate 'perp' compass points, if needed.
    for (unsigned e = 0; e<2; e++) {
        auto b = chart.GetBlockByName(ends[e].pos[0].blocks.front().name, {}, true, true);
        if (ends[e].dir.number==-2)
            ends[e].dir.number = b[0]->GetPerpendicularDir(ends[e].pos[0].edge, ends[e].pos[1].edge);
    }

    //If we start and end from the same points
    if (ends[0].pos[0].IsSame(ends[1].pos[0]) && ends[0].pos[1].IsSame(ends[1].pos[1]) && route_via.empty()) {
        _ASSERT(ends[0].is_block);
        //OK, this is an a->a arrow. Add compass points if needed
        if (ends[0].dir.number==-1) {
            if (ends[1].dir.number==-1)
                //Both compass ports unspecified
                ends[1].dir.number=90; //set the target to east
            ends[0].dir.number = fmod_negative_safe(ends[1].dir.number-90, 360.);
        } else if (ends[1].dir.number==-1)
            fmod_negative_safe(ends[0].dir.number+90, 360.);
    }

    //Special handling for horizontal and vertical
    if (style.routing == EArrowRouting::Horizontal ||
        style.routing == EArrowRouting::Vertical ||
        style.routing == EArrowRouting::Grid)
        if (LayoutHorizVert(canvas, style.routing == EArrowRouting::Horizontal ? 1 : 
                                    style.routing == EArrowRouting::Vertical ? -1 : 0))

            return { path_visible.CrossPoints(false, nullptr, block).empty() ?  15U : 13U,
                     path_visible.empty() ? 0 : path_visible.front().GetLength() };

    //All branches that set routing to straight fall through to here.
    std::vector<XY> vias;
    std::vector<XY> points_to_avoid;
    std::vector<bool> is_hard;
    std::vector<std::string> descr;
    std::vector<FileLineCol> linenum;
    vias.reserve(2+route_via.size());
    is_hard.reserve(2+route_via.size());
    descr.reserve(2+route_via.size());
    linenum.reserve(2+route_via.size());

    /** Structure containing data for an end, which has a direction.
     * We have three relevant points to such an end.
     * - the ultimate end is the original end specification: a block, a port or a coordinate.
     * - the leave_block is the point on the edge of the block in the proscribed dir.
     *      This is where the arrow leaves the block. If no block or port is outside this
     *      equals the ultimate end.
     * - the waypoint is of the correct distance from the leave_block and is used to lay out
     *      the path of the arrow. Then afterwards, we add a section from ultimate_end to the
     *      waypoint.*/
    struct CompassEnd
    {
        const BlockBlock* block = nullptr; ///<The end-block (if any)
        Contour avoid;                     ///<The end block expanded (if any) by its distance. (Since the end blocks are added to route_cross.)
        XY ultimate_end;                   ///<The true endpoints of the arrow (e.g., in the middle of the blocks). In 'vias' we only have the waypoint.
        XY leave_block;                    ///<The point where the arrow leaves the block (in the specified dir) for coordinates (or if port outside block) it == ultimate_end
        double distance;                   ///<This is how far we place the waypoint from the block (or coordinate)
        double len_cut;                    ///<This is how much we cut from the ready path before adding the ultimate end
        double control_point_len;          ///<This is how far the control points will be for the ultimate end_curve
    };
    std::array<CompassEnd, 2> wayp;

    for (unsigned e = 0; e<2; e++) {
        XY d;
        for (unsigned xy = 0; xy<2; xy++) {
            auto c = chart.GetCoord(ends[e].pos[xy], xy, parent);
            if (!c) {
                _ASSERT(0);
                chart.Error.Error(ends[e].file_pos, "Cant translate this to coordinates. Ignoring arrow.");
                redo = valid = false;
                chart.Progress.DoneBulk(ESections::LAYOUT_ARROWS);
                return {};
            }
            d[xy] = *c; // cppcheck-suppress uninitvar
        }
        //Add pixel offset
        d += ends[e].offset_pixel; //TODO: this may take us out of the block - would look strange and we will throw ASSERTs left and right, but hey.

        //Check if we need to apply the default clip_block
        if (ends[e].is_block) {
            //add percentage offset
            auto b = chart.GetBlockByName(ends[e].pos[0].blocks.front().name, {}, true, true); //no prefix needed, by now these must be resolved unique names
            d += b[0]->outer_line.Spans().Scale(ends[e].offset_percent/100);
            //decide if we shall clip
            XY dummy;
            if (style.route_clip_block)
                ends[e].clip_block = *style.route_clip_block;
            else //if user has not set, we clip if specified as a block or are inside
                ends[e].clip_block = !ends[e].from_coordinate ||
                                     b[0]->GetVisibleCover().Distance(d, dummy)<0.01;
            //Decide if we shall de-overlap
            ends[e].deoverlap = style.route_arrow_distance.value_or(0)>0 &&
                                (!ends[e].from_coordinate ||
                                 b[0]->GetVisibleCover().Distance(d, dummy)<0.01);
        } else {
            ends[e].clip_block = ends[e].deoverlap = false;
        }
        if (ends[e].clip_block) {
            //If we clip, also avoid the block
            if (!style.route_cross) style.route_cross.emplace();
            if (!style.route_cross_parent) style.route_cross_parent.emplace();
            style.route_cross->push_back(ends[e].pos[0].blocks.front());
            style.route_cross_parent->push_back(ends[e].pos[0].blocks.front());
        }
        
        points_to_avoid.push_back(d);
        //Handle 'direction'
        if (ends[e].dir.number!=-1) {
            _ASSERT(style.distance);
            if (ends[e].is_block) {
                wayp[e].block = chart.GetBlockByName(ends[e].pos[0].blocks.front().name, {}, true, true)[0];
                //Calculate distance for the waypoint
                //If the user did not specify an explicit distance for this block (or it is from a coordinate)
                //and the generic distance is smaller than def_distance_cp, we increase distance to def_distance_cp.
                const auto i = std::find_if(style.distance_per_block->begin(), style.distance_per_block->end(),
                                            [&wayp, e](const auto &spn) {return wayp[e].block->name_unique == spn.name; });
                //if the user specified a distance for a coordinate-based arrow end, use that
                if (ends[e].dir_distance>0)
                    wayp[e].distance = ends[e].dir_distance;
                //else if the user specified a distance=<block>@<dist> for this ending block, use that
                else if (style.distance_per_block->end() != i)
                    wayp[e].distance = i->number;
                //else use the larger of the generic 'distance' attr and the default
                else
                    wayp[e].distance = std::max(*style.distance, BlockChart::def_distance_cp);
            } else {
                if (ends[e].dir_distance>0)
                    wayp[e].distance = ends[e].dir_distance;
                else //(user has not specified a waypoint distance)
                    wayp[e].distance = std::max(*style.distance, BlockChart::def_distance_cp);
            }
            wayp[e].distance += GetHalfLineWidth(); //to avoid clipping
            if (wayp[e].distance<2)
                wayp[e].distance = 2;
            wayp[e].len_cut = wayp[e].distance;
            wayp[e].control_point_len = wayp[e].distance * 0.7;  //smaller to make joining smoother

            wayp[e].ultimate_end = d;
            const double radian = (ends[e].dir.number-90)*M_PI/180; //-90 because zero is north
            const XY vector = XY(cos(radian), sin(radian))*wayp[e].distance;
            if (wayp[e].block){// && inside(wayp[e].block->GetVisibleCover().IsWithin(d))) {
                Contour exp = wayp[e].block->GetVisibleCover().CreateExpand(wayp[e].distance);
                auto cut = exp.CutExAll2(d, d+vector);
                if (cut.size()) {
                    //_ASSERT(cut.size()==2 && cut.front().other.pos<=0 && cut.back().other.pos>=0);
                    d = cut.back().xy; //the cp closer to d+vector
                    //Now d is the point where the arrow shall leave the expanded contour - the waypoint in short
                    wayp[e].avoid = std::move(exp);
                    //Now calculate 'leave_block'
                    cut = wayp[e].block->GetVisibleCover().CutExAll2(wayp[e].ultimate_end, wayp[e].ultimate_end+vector);
                    if (cut.size()) {
                        //_ASSERT(cut.size()==2 && cut.front().other.pos<=0 && cut.back().other.pos>=0);
                        wayp[e].leave_block = cut.back().xy;
                    } else
                        wayp[e].leave_block = wayp[e].ultimate_end;
                } else {
                    d += vector;
                    wayp[e].leave_block = wayp[e].ultimate_end;
                }
            } else {
                d += vector;
                wayp[e].leave_block = wayp[e].ultimate_end;
            }
            ends[e].dir_segment = Path{ wayp[e].leave_block, d };
            //Now d is where the waypoint should be (wayp[e].distance) far away from the port (if port is outside)
            //or from the contour of the block in the right dir (if the port is inside)
        }
        vias.emplace_back(d);
        is_hard.push_back(true);
        descr.push_back(e==0 ? "its start": "its end");
        linenum.push_back(ends[e].file_pos);
    }
    Contour bbs_blocks = chart.Blocks.GetContourForArrows(points_to_avoid, true,
                                                          *style.route_cross,
                                                          *style.route_cross_parent,
                                                          style.distance.value_or(1.5),
                                                          *style.distance_per_block,
                                                          GetHalfLineWidth());

    //Now note that in case of block ends, the blocks themselves (and children) are NOT part of bbs (but parents are).
    //(If we have waypoints, the cover of the end blocks are, however, in wayp[].avoid, and will later be added.)
    //If we have waypoints, any block covering the waypoint IS part of bbs (including their parents, unless
    //they are also parent of the block ends).

    //Determine the coordinates of the via points the user specified
    for (XY via; const auto &v : route_via) {
        switch (v.index()) {
        default:
            _ASSERT(0);
            break;
        case 1:
            via.x = chart.GetCoord(std::get<1>(v)[0], false, parent).value_or(DBL_MAX);
            via.y = chart.GetCoord(std::get<1>(v)[1], true , parent).value_or(DBL_MAX);
            if (via.x!=DBL_MAX && via.y!=DBL_MAX) {
                vias.push_back(via);
                is_hard.push_back(true);
                linenum.push_back(std::get<1>(v)[0].blocks.front().file_pos.start);
            }
            break;
        case 0:
            const auto b = chart.GetBlockByName(std::get<0>(v).block, parent, true, true)[0];
            const double g = b->GetDistanceForMe(style.distance.value_or(1.5),
                                                 *style.distance_per_block)
                + GetHalfLineWidth();
            const Contour area_exp = b->GetAreaToDraw().CreateExpand(g);
            const Block o = area_exp.GetBoundingBox();
            switch (std::get<0>(v).pos) {
            case ViaAttrSoloBlock::LEFT:   via = {o.x.from-g, o.y.MidPoint()}; break;
            case ViaAttrSoloBlock::RIGHT:  via = {o.x.till+g, o.y.MidPoint()}; break;
            default: _ASSERT(0); FALLTHROUGH;
            case ViaAttrSoloBlock::CENTER: via = b->outer_line.Centroid(); break;
            case ViaAttrSoloBlock::TOP:    via = {o.x.MidPoint(), o.y.from-g}; break;
            case ViaAttrSoloBlock::BOTTOM: via = {o.x.MidPoint(), o.y.till+g}; break;

            case ViaAttrSoloBlock::BOTTOMLEFT: via = o.LowerLeft()+XY(-g, +g); break;
            case ViaAttrSoloBlock::BOTTOMRIGHT: via = o.LowerRight()+XY(+g, +g); break;
            case ViaAttrSoloBlock::TOPLEFT: via = o.UpperLeft()+XY(-g, -g); break;
            case ViaAttrSoloBlock::TOPRIGHT: via = o.UpperRight()+XY(+g, -g); break;
            }
            //Now take the last outermost crosspoint on the Centroid->via section.
            const Edge edge(o.Centroid(), via);
            via = edge.Pos2Point(area_exp.Cut(edge).till+0.00001);
            vias.push_back(via);
            is_hard.push_back(false);
            linenum.push_back(std::get<0>(v).block_pos);
        }
        descr.push_back("one of its 'via' attribute");
    }
    //first two elements are start, end; then come the vias.
    //rotate end to the back
    std::rotate(vias.begin()+1, vias.begin()+2, vias.end());
    std::rotate(is_hard.begin()+1, is_hard.begin()+2, is_hard.end());
    std::rotate(descr.begin()+1, descr.begin()+2, descr.end());
    std::rotate(linenum.begin()+1, linenum.begin()+2, linenum.end());

    _ASSERT(is_hard.front() && is_hard.back());

    //Now check if a waypoint due to direction falls into a blocked area
    for (unsigned e = 0; e<2; e++) {
        if (ends[e].dir.number==-1) continue; //No port or dir specified: We don't know where we will exit.
        if (contour::inside(bbs_blocks.IsWithin(wayp[e].leave_block))) {
            //wayp[e].leave_block is inside covered by bbs.
            //Since the end block is not part of bbs (if any) this means that
            //a neighbouring block + distance actually covers the leave_block point.
            //In this case we cannot really do an arrow.
            //TODO: Find out which block (expanded by distance) covers this point and report it to the user.
            chart.Error.Error(file_pos.start, "A neighbouring block is too close to the port in the specified dir. Cannot route.");
            chart.Progress.DoneBulk(ESections::LAYOUT_ARROWS);
            redo = false;
            valid = false;
            return {};
        }
        //Take a ray from the block_leave to the waypoint and see where it
        //enters the blocked area
        XY &waypoint = e==0 ? vias.front() : vias.back();
        auto cut = bbs_blocks.CutExAll(wayp[e].leave_block, waypoint);
        if (cut.size()<2) continue; //we only touch that is OK
        //Find the smallest crosspoint between [0..1]
        auto i = std::min_element(cut.begin(), cut.end(), [](const contour::CPData&a, const contour::CPData&b)
                                    //if both a and b is inside (0..1) or both outside, we return their relation. Else the one in (0..1) is smaller
                                    {return (0<a.other.pos && a.other.pos<1) == (0<b.other.pos && b.other.pos<1) ?
                                            a.other.pos<b.other.pos :
                                            0<a.other.pos && a.other.pos<1; });
        //OK, we know that leave_block is not covered by bbs.
        double pos = i->other.pos;
        if (pos<=0 || 1<=pos) continue; //we bump into bbs well after the waypoint only (or well before)
        //Set waypoint to just where we enter bbs.
        pos -= 0.0001;
        waypoint = (waypoint - wayp[e].ultimate_end)*pos + wayp[e].ultimate_end;
        wayp[e].distance *= pos;
        wayp[e].len_cut *= pos;
        wayp[e].control_point_len *= pos;
        //update 'avoid' for the smaller distance
        if (wayp[e].block)
            wayp[e].avoid = wayp[e].block->GetVisibleCover().CreateExpand(wayp[e].distance);
    }

    //Avoid the start/end block of an arrow, if a direction and waypoint was set.
    for (unsigned e = 0; e<2; e++)
        if (wayp[e].block)
            bbs_blocks += wayp[e].block->GetVisibleCover().Expand(GetHalfLineWidth());

    //We have one mandatory area to avoid
    // 0. The list of blocks to go around (in bbs_blocks) (includes start/end blocks if a waypoint is set for them)
    //We have now 3 optional areas to avoid:
    // 1. The cover of other arrows in 'block'
    // 2. The expanded area of the starting block (if a waypoint is set for start due to a fix direction)
    // 3. The expanded area of the ending block (if a waypoint is set for end due to a fix direction)
    //Determine, which of the optional areas can be added to the area to avoid, while still allowing routing.
    std::array<std::optional<Contour>, 16> C; //The index is the binary combination: is this area added?
    C[1] = std::move(bbs_blocks);
    C[2] = std::move(block);
    C[4] = std::move(wayp[0].avoid);
    C[8] = std::move(wayp[1].avoid);
    //wayp[e].avoid is unusable beyond this point

    unsigned index = 15;
    for (size_t i = 0; i<vias.size()-1; i++) {
        const unsigned ndx = CheckSegmentRoutability(C, index, vias[i], vias[i+1], descr[i], descr[i+1], linenum[i], linenum[i+1]);
        if (ndx==0) {
            chart.Progress.DoneBulk(ESections::LAYOUT_ARROWS);
            redo = false;
            return {}; //unreachable
        } else
            index &= ndx;
    }
    //Now C[index] holds the union of the maximum set of optional covers we can route around.
    //TODO: If we cannot route arrows because of one of the optional areas to avoid (so index
    //here is not 15, we will have either failed at de-overlapping arrows with same endings
    //(if 'block' cannot be added to bbs), or will route too close to the start/end block of
    //an arrow. These are known to look bad, so we should maybe backtrack and try a different
    //de-overlapping strategy (offset_pixel) or routing order (see BlockChart::CheckArrowOverlap()).
    //That can take pretty long and can get pretty complicated to code, so I leave it for later.
    Contour bbs = std::move(*C[index]);
    std::ranges::for_each(C, [](auto o) { o.reset(); }); //free memory
    //Prune parts of the blocks to avoid - prune all holes & similar
    for (unsigned u = 0; u<bbs.size(); u++)
        //Now we know that all the waypoints are in the same hole or in no hole at all
        //so we only need to check the first for containment
        if (bbs[u].Outline().IsWithin(vias[0])==contour::WI_INSIDE)
            //If the points are in a hole (of a HoledSimpleContour) take that (and its contents) as boundary to avoid.
            bbs.RemoveAllBut(u); //the for cycle will end after this
        else
            bbs.ClearHolesIn(u);

    std::list<PathCandidate> paths;
    PathScore ret;

    //Calculate all segments between hard vias and combine all of them.
    //Special case: a single segment between the same points
    if (vias.size()==2 && vias[0].DistanceSqr(vias[1])<THRESHOLD*THRESHOLD) {
        //If we have no waypoints, give a warning to the user
        if (ends[0].dir.number==-1 && ends[1].dir.number==-1)
            chart.Error.Warning(file_pos.start, "This arrow starts and ends at the same coordinate and is skipped.");
        else {
            path = { vias[0], vias[1] }; //these are waypoints and additional legs will be added
            ret.index = 15; //leave length 0, turn 0, for via0==via1
        }
        //Skip the whole path computation shabang below
    } else {
        //Cycle through the waypoints segments between hard waypoints
        for (size_t from = 0, to = std::find(is_hard.begin()+1, is_hard.end(), true)-is_hard.begin();
             from<is_hard.size()-1;
             from = to, to = std::find(is_hard.begin()+from+1, is_hard.end(), true)-is_hard.begin()) {
            auto n = LayoutSegment({ &vias.front()+from, to+1-from }, bbs,
                                   style.routing.value_or(EArrowRouting::Straight), chart.arrow_ambigous_percent,
                                   &chart.Progress);
            if (n.size()==0) {
                //A zero length segment came back.
                if (vias[from]!=vias[to]) {
                    //If this was between two different points,
                    //it is an error and we abort
                    paths.clear();
                    break;
                }
            } else if (paths.size()==0) {
                paths = std::move(n);
            } else {
                //Direct delta of existing path alternatives appended with the new ones
                std::list<PathCandidate> tmp;
                for (auto &p1 : paths)
                    for (auto &p2 : n)
                        tmp.push_back(std::move(p1)+std::move(p2));
                paths = std::move(tmp);
            }
        }
        //Adjust the 'turn' and 'length' values of path candidates with waypoint.
        //Penalize paths that do not continue in the direction of their waypoint 
        //and also add the distance from the waypoint to where the path leaves the block.
        //This will result in the full path of the visible (but still polygon) path.
        for (unsigned u=0; u<2; u++)
            if (ends[u].dir.number != -1)
                for (auto &p : paths) 
                    p.UpdateTurnsLengthWithWaypoints(u, wayp[u].leave_block);
            
        path.clear();
        path_visible.clear();
        if (paths.size()==0) {
            chart.Progress.DoneBulk(ESections::LAYOUT_ARROWS);
            redo = false;
            return {};
        }
        auto &P = *std::ranges::min_element(paths, PathScoreComp(style.route_factor.value_or(0.5)));
        //Kill 'block_side', so that we combine all kinds of edges
        for (auto &e : P.path) e.block_side = ESide::END;
        P.path.Simplify(THRESHOLD);
        if (P.path.empty()) {
            _ASSERT(0);
            chart.Progress.DoneBulk(ESections::LAYOUT_ARROWS);
            redo = false;
            return {};
        }
        ret = { index, P.length, P.turn };
        std::ranges::copy(P.path, std::back_inserter(path_straight));
        //From now on we can only make paths longer, so we don't check size again
        //Smoothen for curvy paths
        if (style.routing.value_or(EArrowRouting::Polygon)==EArrowRouting::Curvy) {
            constexpr double max_ratio = 0.4;
            constexpr double dec_ratio = 0.05;
            for (auto &e : P.path)
                e.ratio = { max_ratio, max_ratio };
            while (true) {
                contour::EdgeVector<EdgeWithSide> curvy = P.path;
                SmoothenVariableRatio(curvy.begin(), curvy.end(), 2, false, true,
                                      [](const EdgeWithSide&e, double len, double other_len, bool start)
                                      {return e.ratio[!start]*std::min(len, 2*other_len); });
                //Check if we cut into bbs & decrease ratio there
                bool had_cut = false;
                for (auto i = curvy.begin(), j = P.path.begin(); i!=curvy.end(); i++, j++) {
                    if (i->IsStraight()) continue; //straight edges has not been changed so the cannot cut into bbs.
                    //Determine if there is a cut
                    auto cp = bbs.CrossPoints(*i);
                    //remove our endpoints, since those are by definition on the contour of bbs.
                    cp.erase(std::remove_if(cp.begin(), cp.end(), [](const auto &a) {return a.other.pos==0 || a.other.pos==1; }), cp.end());
                    if (cp.size()==0) continue;
                    had_cut = true;
                    //Determine which half had a cut
                    //First determine the halfway point
                    auto cp2 = i->Crossing(*j, false);
                    //take the crosspoint that is further from the ends
                    double delim = -1;
                    for (unsigned u = 0; u<cp2.num; u++)
                        if (delim==-1 || std::max(fabs(delim), fabs(delim-1))<std::max(fabs(cp2.pos_other[u]), fabs(cp2.pos_other[u]-1)))
                            delim = cp2.pos_other[u];
                    std::array<bool, 2> cuts = { false,false };
                    if (delim==-1 || delim==0 || delim==1) {
                        //no crosspoint inside the edge. The curve lies entirely on one side of the straight original.
                        //If one cp equals to start/end, this means we did smoothing only on the other end
                        if (i->GetC1()==i->GetStart()) delim = 0;
                        else if (i->GetC2()==i->GetEnd()) delim = 1;
                        else
                            //If not, it cannot be determined whether the start or end smoothing causes the
                            //transgression into bbs's surface, so we need to cut both end's curviness
                            cuts = { true, true };
                    }
                    //stop the cycle if both ends already identified to be part of the cut
                    for (auto c = cp.begin(); c!=cp.end() && !(cuts[0]&&cuts[1]); c++)
                        if (c->other.pos<delim) cuts[0] = true;
                        else if (c->other.pos>delim) cuts[1] = true;
                    //If we have already reached zero ratio and should decrease further
                    //we stop to avoid infinite loops
                        if ((cuts[0] && j->ratio[0]==0) || (cuts[1] && j->ratio[1]==0)) {
                            had_cut = false;
                            break;
                        }
                        for (unsigned u : {0, 1})
                            if (cuts[u]) {
                                j->ratio[u] = std::max(0., j->ratio[u]-dec_ratio);
                                if (j->ratio[u]<0.001) j->ratio[u] = 0;
                            }
                }
                if (!had_cut) {
                    P.path.swap(curvy);
                    break;
                }
            }
        }
        path.reserve(P.path.size());
        for (auto &e : P.path)
            path.push_back(e);
    }
    //Now prepend/append legs for waypoints used when a direction is requested
    //Note that at this point 'path' can be empty if it started and ended at the same coordinate
    if (ends[0].dir.number!=-1 || ends[1].dir.number!=-1) {
        //ultimate_end[0/1]: the actual port
        //TODO: maybe we should use 'leave_block' instead???
        //vias.front/back=path.front/back: the waypoint of appropriate distance ('wayp[e].distance') from
        //                                  - the contour of the block (if port is inside the block)
        //                                  - the port (if the port is outside the block)
        if (style.routing.value_or(EArrowRouting::Polygon)==EArrowRouting::Curvy) {
            //The direction in which the arrow needs to leave start/end
            const XY vectors[2] = {
                ends[0].dir.number==-1 ?
                     (wayp[1].ultimate_end - path.front().GetStart()).Normalize() : //the other end surely has a waypoint
                     path.front().GetStart() == wayp[0].ultimate_end ?
                         XY(0, 0) :
                         (path.front().GetStart()-wayp[0].ultimate_end).Normalize(),
                ends[1].dir.number==-1 ?
                     (wayp[0].ultimate_end - path.back().GetEnd()).Normalize() : //the other end surely has a waypoint
                     path.back().GetEnd()    == wayp[1].ultimate_end ?
                          XY(0, 0) :
                          (path.back().GetEnd()  -wayp[1].ultimate_end).Normalize()
            };
            //where the arrow crosses the block contour (if port is inside the block)
            //Or the path start/end if no waypoint
            const XY visible_end[2] = {
                ends[0].dir.number==-1 ? path.front().GetStart() :
                    vias.front()-vectors[0]*wayp[0].distance,
                ends[1].dir.number==-1 ? path.back().GetEnd() :
                    vias.back() -vectors[1]*wayp[1].distance
            };
            const double len = path.GetLength();
            if (len < (ends[0].dir.number!=-1 ? wayp[0].len_cut : 0) +
                      (ends[1].dir.number!=-1 ? wayp[1].len_cut : 0)) {
                //path too short - replace with a single bezier edge
                path.clear();
                const double cp_len[2] = {
                    ends[0].dir.number==-1 ? BlockChart::def_distance_cp*0.7 : wayp[0].control_point_len,
                    ends[1].dir.number==-1 ? BlockChart::def_distance_cp*0.7 : wayp[1].control_point_len,
                };
                if (ends[0].dir.number!=-1)
                    path.append(Edge(wayp[0].ultimate_end, visible_end[0]));
                path.append(Edge(visible_end[0], visible_end[1],
                                 visible_end[0]+vectors[0]*cp_len[0], visible_end[1]+vectors[1]*cp_len[1]));
                if (ends[0].dir.number!=-1)
                    path.append(Edge(visible_end[1], wayp[1].ultimate_end));
            } else {
                //path long enough, trim it.
                if (ends[0].dir.number!=-1) {
                    path.TruncateStart(wayp[0].len_cut);
                    //control point towards the path
                    const XY cp = (path.PrevTangentPoint(path.GetStartPos(), false)-path.front().GetStart()).Normalize()*wayp[0].control_point_len
                        +path.front().GetStart();
                    const Edge e(visible_end[0], path.front().GetStart(),
                                 visible_end[0]+vectors[0]*wayp[0].control_point_len, cp);
                    path.prepend(e); //do as separate step, since we re-use path.front(), which may be invalid during insert
                    path.prepend(wayp[0].ultimate_end);
                }
                if (ends[1].dir.number!=-1) {
                    path.TruncateEnd(wayp[1].len_cut);
                    const XY cp = (path.NextTangentPoint(path.GetEndPos(), false)-path.back().GetEnd()).Normalize()*wayp[1].control_point_len
                        +path.back().GetEnd();
                    const Edge e(path.back().GetEnd(), visible_end[1],
                                 cp, visible_end[1]+vectors[1]*wayp[1].control_point_len);
                    path.append(e);
                    path.append(wayp[1].ultimate_end);
                };
            }
            path.Simplify(THRESHOLD); //to cater for the case of the port being outside
        } else {
            //polygon routing
            if (ends[0].dir.number!=-1) path.prepend(wayp[0].ultimate_end);
            if (ends[1].dir.number!=-1) path.append(wayp[1].ultimate_end);
        }
    }
    //Make corners round
    if (style.routing.value_or(EArrowRouting::Polygon)!=EArrowRouting::Curvy)
        switch (style.line.corner.value_or(ECornerType::NONE)) {
        default: _ASSERT(0); FALLTHROUGH;
        case ECornerType::NONE:
        case ECornerType::NOTE:
            break;
        case ECornerType::ROUND:
            path = path.CreateRounded(style.line.radius.value_or(10), false, false);
            break;
        case ECornerType::BEVEL:
            path = path.CreateRounded(style.line.radius.value_or(10), true, false);
            break;
        }
    _ASSERT(path.IsConnected());
    PathFound(canvas);
    ret.shift = ends[0].offset_pixel.length() + ends[1].offset_pixel.length();
    return ret;
}

void Arrow::ResetPath() const {
    path.clear();
    path_straight.clear();
    path_visible.clear();
    clip_ah.clear();
}


void Arrow::PostPosProcess(Canvas &canvas, Chart *c)
{
    if (!valid) return;
    BlockInstruction::PostPosProcess(canvas, c);
    if (area.IsEmpty()) return;
    chart.IncreaseTotal(area.GetBoundingBox());
    chart.AllCovers += area;
}

void Arrow::Draw(Canvas &canvas) const
{
    if (!valid) return;
    if (path.IsEmpty()) return;

    if (canvas.does_graphics()) {
        canvas.ClipInverse(clip_ah+clip_block);
        canvas.Line(path, style.line);
        canvas.UnClip();
        style.arrow.read().Draw(canvas, path, style.line);
    } else {
        Path p = path;
        p.ClipRemove(clip_block, true);
        canvas.Add(GSPath(std::move(p), style.line,
                          &style.arrow.read().endArrowHead, &style.arrow.read().startArrowHead));
    }
}


