/*
    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 arcs.h The declaration of classes for arc bases, arrows and verticals.
 * @ingroup libmscgen_files */


#if !defined(ARCS_H)
#define ARCS_H

#include "cgen_color.h"
#include "style.h"
#include "mscentity.h"
#include "numbering.h"
#include "csh.h"
#include "area.h"
#include "mscarrow.h"

namespace msc {

/** Enumerates the type of arrow, box and divider symbols (->, => or <->, etc). */
enum class EArcSymbol : uint8_t
{
    INVALID = 0,             ///<Invalid value
    ARC_SOLID,               ///<A solid, unidir arrow: -> or <- (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_SOLID_BIDIR,         ///<A solid, bidirectional arrow: <-> (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_DOTTED,              ///<A dotted unidir arrow: > or < (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_DOTTED_BIDIR,        ///<A dotted bidir arrow: <> (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_DASHED,              ///<A dashed unidir arrow: >> or << (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_DASHED_BIDIR,        ///<A dashed bidir arrow: <<>> (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_DOUBLE,              ///<A double lined unidir arrow: => or <= (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_DOUBLE_BIDIR,        ///<A double lined bidirectional arrow: <=> (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_DBLDBL,              ///<An mscgen double lined double unidir arrow: =>> or <<= (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_DBLDBL_BIDIR,        ///<An mscgen double lined double bidirectional arrow: <<=>> (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_DOUBLE2,             ///<This is a ==> or <==, but also used for mscgen unidir arrow with colon: :> or <: (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_DOUBLE2_BIDIR,       ///<This is a <==>, but also used for mscgen bidirectional arrow with colon: <:> (DirArrow, SelfArrow, BlockArrow, ArcVertical)
    ARC_UNDETERMINED_SEGMENT,///<An arrow segment of undefined type: - (DirArrow, BlockArrow)
    BOX_SOLID,               ///<A solid box: -- (Box, Pipe, ArcVertical)
    BOX_DOTTED,              ///<A dotted box: .. (Box, Pipe, ArcVertical)
    BOX_DASHED,              ///<A dashed box: ++ (Box, Pipe, ArcVertical)
    BOX_DOUBLE,              ///<A double box: == (Box, Pipe, ArcVertical)
    BOX_UNDETERMINED_FOLLOW, ///<A (subsequent) box in a box segment, without a type specifier (Box)
    DIV_DISCO,               ///<A discontinuity in time line: ... (Divider)
    DIV_DIVIDER,             ///<A divider: --- (Divider)
    DIV_VSPACE,              ///<No arc, just space (maybe with label) (Divider)
    DIV_NUDGE,               ///<The nudge command
    DIV_TITLE,               ///<The title command
    DIV_SUBTITLE,            ///<The subtitle command
};
/** True if the arc symbol represents an arrow.*/
inline bool IsArcSymbolArrow(EArcSymbol t) { return t<=EArcSymbol::ARC_UNDETERMINED_SEGMENT && t>EArcSymbol::INVALID; }
/** True if the arc symbol is bidirectional.*/
inline bool IsArcSymbolBidir(EArcSymbol t) { _ASSERT(IsArcSymbolArrow(t));  return (unsigned(t)&1) == 0 && t<EArcSymbol::ARC_UNDETERMINED_SEGMENT && t>EArcSymbol::INVALID; }
/** True if the arc symbol represents a box (==,--,..,++).*/
inline bool IsArcSymbolBox(EArcSymbol t) { return t<=EArcSymbol::BOX_UNDETERMINED_FOLLOW && t>=EArcSymbol::BOX_SOLID; }
/** True if the arc symbol represents a divider (---,|||,...,nudge, title, subtitle, vspace.*/
inline bool IsArcSymbolDivider(EArcSymbol t) { return t>=EArcSymbol::DIV_DISCO; }
/** Return the arrow symbol (bidir or only single dir) corresponding to the specified box symbol, when it comes to line style.
 * -- becomes -> or <->; == becomes => or <=>, etc.*/
inline EArcSymbol ConvertBoxSymbol2ArrowSymbol(EArcSymbol t, bool bidir) { _ASSERT(IsArcSymbolBox(t));  return EArcSymbol((int(t)-int(EArcSymbol::BOX_SOLID))*2 + int(EArcSymbol::ARC_SOLID) + int(bidir)); }

/** The direction of an arrow in the text file */
enum class EDirType {
    INDETERMINATE, ///<An arrow segment of unspecified direction
    RIGHT,         ///<An arrow pointing left-to-right. This is the forward direction.
    LEFT,          ///<An arrow pointing right-to-left. This is the reverse direction.
    BIDIR          ///<A bidirectional arrow.
};

/** Indicates if the message was lost and where. */
enum class EArrowLost {
    NOT,     ///<The arrow is not lost
    AT_SRC,  ///<The arrow is lost close to the src entity
    AT_DST   ///<The arrow is lost close to the dst entity
};

/** Collects data about an arrow segments (dashed/solid/dotted/double and
 * whether it indicates a lost message using an asterisk */
struct ArrowSegmentData {
    EArcSymbol type;           ///<The type of the arrow such as ARC_SOLID or BOX_DASHED
    EArrowLost lost;           ///<Whether there was a loss indication (*) or not.
    FileLineColRange lost_pos; ///<The location of the asterisk if any.
    ArrowSegmentData() = default;
    ArrowSegmentData(EArcSymbol t, EArrowLost l, const FileLineColRange &p = FileLineColRange()) :
        type(t), lost(l), lost_pos(p) {}
    explicit ArrowSegmentData(EArcSymbol t) :
        type(t), lost(EArrowLost::NOT) {}
};

/** A structure used to hold an arrow type & loss and a direction. Used with Vertical. */
struct ArcTypePlusDir
{
    ArrowSegmentData arc; ///<What kind of an arrow (or box) are we, and where are we lost (if at all)
    EDirType dir;         ///<Are we forward, backward or bidir or without arrowhead.
    ArcTypePlusDir() = default;
    ArcTypePlusDir(EArcSymbol t, EArrowLost l, EDirType d, const FileLineColRange &p = FileLineColRange()) :
        arc(t, l, p), dir(d) {}
    ArcTypePlusDir(const ArrowSegmentData *sd, EDirType d) :
        arc(*sd), dir(d) { delete sd; }
    ArcTypePlusDir(EArcSymbol t, EDirType d) :
        arc(t), dir(d) { }
};

/** A structure holding a 'start before' or 'end after' clause.
* It only applies to DirArrow (and BlockArrow by inheritance).
* When the arrow have a special ending its src and its dst will
* equal to LSide or RSide, but of course the arrow shall end
* at the location pointed by this structure.*/
struct ArrowEnding
{
    EntityRef   entity = nullptr; ///<The iterator of the entity we refer to either from AllEntities or ActiveEntities
    FileLineCol linenum;          ///<The position of the 'start before' or 'end after' clause, if generated from source. In calls to AddStartPos() and AddEndPos() this is just the value specified by the user and not the absolute offset.
    double      offset = 0;       ///<The offset from the middle of the entity line. Up to the end of AddAttributeList() larger value is more towards the end of the arrow; after that positive is towards right, negative is towards left.
    ArrowEnding() noexcept = default;
    ArrowEnding(MscChart &chart, std::string_view e, const FileLineColRange &l, double o = 0);
    explicit ArrowEnding(EntityRef e, double o = 0, const FileLineCol &l = FileLineCol()) : entity(e), linenum(l), offset(o) {}
    ArrowEnding(const ArrowEnding&) = default;
    /** Compares position of two valid endings. */
    bool operator < (const ArrowEnding& o) const noexcept { _ASSERT(entity && o.entity); return std::tie(entity->pos_exp, offset)<std::tie(o.entity->pos_exp, o.offset); }
    bool operator > (const ArrowEnding &o) const noexcept { _ASSERT(entity && o.entity); return std::tie(entity->pos_exp, offset)>std::tie(o.entity->pos_exp, o.offset); }
    /** Compares position of two valid endings. */
    bool operator ==(const ArrowEnding &o) const noexcept { return entity == o.entity && offset == o.offset; }
};


/** Describes if and how two elements can be joined. */
struct JoinableResult
{
    enum EJoinType
    {
        CURLY,                          ///<Two arrows connect in a curly fashion
        SETTLED,                        ///<Two elements connect that need no update of the endings (everything, except arrow-to-box)
        NEED_UPDATE_FIRST,              ///<An arrow connects to a box - the endpoint of the arrow needs to be updated in Width()
        NEED_UPDATE_SECOND,             ///<An arrow connects to a box - the endpoint of the arrow needs to be updated in Width()
        CURLY_NEED_UPDATE_SECOND,       ///<Two arrows connect in a curly fashion, but the second needs update
        ERROR_BAD_END_FIRST,            ///<One of the endpoints needs to be fully specified (a box like '..')
        ERROR_BAD_END_SECOND,           ///<One of the endpoints needs to be fully specified (a box like '..')
        ERROR_BOTH_UNSPECIFIED,         ///<These two ends cannot join for they are both unspecified
        ERROR_FIRST_CAN_ONLY_TAKE_SPEC, ///<One of the endpoints can only take an unspecified other end (like boxes)
        ERROR_SECOND_CAN_ONLY_TAKE_SPEC,///<One of the endpoints can only take an unspecified other end (like boxes)
        ERROR_OVERLAP,                  ///<These two ends cannot join for they overlap or generic error
    } type;
    static bool IsError(EJoinType t) { return t>=ERROR_BAD_END_FIRST; }
    static bool IsCurly(EJoinType t) { return t==CURLY_NEED_UPDATE_SECOND || t==CURLY; }
    bool IsError() const { return IsError(type); }
    bool IsCurly() const { return IsCurly(type); }
    bool left_to_right;               ///<The next element after the second entity comes to the right
    std::pair<bool, bool> is_src;     ///<True if the first/second element connects here with its 'src' member.
    const char *error_msg_no_join;    ///<The error message for ERROR_BAD_END_*
    const char *error_msg_no_join_add;///<The error message for ERROR_BAD_END_*
    JoinableResult(EJoinType t) : type(t) { _ASSERT(IsError(t)); }
    JoinableResult(EJoinType t, bool l, bool fi, bool se) : type(t), left_to_right(l), is_src(fi, se) {}
    JoinableResult(EJoinType t, const char *m, const char *a) : type(t), error_msg_no_join(m), error_msg_no_join_add(a) {}
    bool operator < (const JoinableResult &o) const { return type<o.type; }
};

/** Describes a side of elements that can participate in a join series. */
struct EndConnection : public ArrowEnding
{
    const char *error_msg_no_join;     ///<If non-nullptr, this side does not accept joins and here is the error.
    const char *error_msg_no_join_add; ///<Further info. Interpreted only if error_msg_no_join is non-nullptr
    bool is_src;                       ///<This is the 'src' member of its element.
    bool body_is_left;                 ///<True if the body of the element is left of this position
    bool accepts_curly_join;           ///<True, if an arrow can curly join here
    bool can_curly_join;               ///<True, if we can do a curly join
    bool is_unspecified;               ///<True, if this is the leftmost or rightmost specific side, but the element (arrow) may extend further
    bool is_indicator;                 ///<True if this is an end to an indicator. We can curly join to it, in case of need.
    EndConnection() : error_msg_no_join(nullptr), error_msg_no_join_add(nullptr) {}
    EndConnection(const ArrowEnding&o) : ArrowEnding(o), error_msg_no_join(nullptr), error_msg_no_join_add(nullptr) {}
    /** Compares position of two valid endings, taking body is_left into account. */
    bool operator < (const EndConnection &o) const { return std::tie(entity->pos_exp, offset,body_is_left)<std::tie(o.entity->pos_exp, o.offset, o.body_is_left); }
    /** Returns if the two endings can connect, and if yes, how, and if no, why not.*/
    JoinableResult CanJoinTo(const EndConnection &next) const;
};

class DistanceRequirements; //defined in msc.h
struct BoxSignature; //defined in boxes.h

                     /** Enumerates all bulk sections. These are ones that are done only once.*/
enum class EBulkSection
{
    PARSE,
    AUTOPAGINATE,   ///<Automatic pagination
    MAX_BULK_SECTION
};
/** Enumerates the sections, we have to register the number of items to be processed in each section.*/
enum class EArcSection
{
    POST_PARSE,     ///<PostParseProcess
    FINALIZE_LABELS,///<FinalizeLabels
    WIDTH,          ///<Width calculation, including entity spacing
    LAYOUT,         ///<Layout calculation
    PLACEWITHMARKERS,///MscChart::PlaceWithMarkers
    NOTES,          ///<Note placement
    POST_POS,       ///<MscChart::PostPosProcess
    DRAW,           ///<Drawing
    MAX_ARC_SECTION///<Not used, the max value
};
/** Enumerates all the element categories: each can be processed in each section.*/
enum class EArcCategory
{
    REMAINDER,
    INDICATOR,
    SELF_ARROW,
    DIR_ARROW,
    BLOCK_ARROW,
    VERTICAL,
    BOX,
    BOX_SERIES,
    PIPE,
    PIPE_SERIES,
    DIVIDER,
    PARALLEL,
    ENTITY,
    NEWPAGE,
    TINY_EFFORT,
    EMPTY,
    SYMBOL,
    COMMENT,  //includes endnotes
    NOTE,
    MAX_CATEGORY
};



/** The base class for all arcs and commands.
 * One object of this type roughly correspond to one semicolon (;) delimited
 * line of input text. */
class ArcBase : public MscElement
{
private:
    bool had_add_attr_list;///<True if we have called AddAttributeList for this arc. TODO: debug only, remove
protected:
    bool   valid;          ///<If false, then construction failed, arc does not exist
    double vspacing;       ///<Contains the spacing *above* this arc. DBL_MIN if compress mechanism is on for this arc (compress attribute set)
    bool   parallel;       ///<If true, subsequent arcs can be drawn beside this.
    bool   overlap;        ///<If true, subsequent arcs can be drawn overlaid on this.
    bool   keep_together;  ///<If true, do not split this by automatic pagination.
    bool   keep_with_next; ///<If true, do not separate this from following element by automatic pagination
    bool   stick_to_prev;  ///<If true, we do not allow elements to be laid out between this and the previous element (relevant in parallel layout)
    string refname;        ///<Value of the "refname" attribute, to reference numbers & others. Empty if none.
    mutable double height; ///<Pixel height of the arc, calculated by Layout()
    static void AttributeNamesSet1(Csh &csh);
    static bool AttributeValuesSet1(std::string_view attr, Csh &csh);
    /** Add the current activation status to the last element in 'vdist'. Used by Width().*/
    void AddEntityLineWidths(DistanceRequirements &vdist);
public:
    FileLineCol join_pos;  ///<The location of the optional 'join' keyword *before* this element. Invalid if no such keyword was specified.
    const EArcCategory myProgressCategory; ///<The category of the arc for calculating progress.

    /** Basic constructor, taking const members and the reference to MscChart we are member of.*/
    ArcBase(EArcCategory c, MscChart *msc);
    /** A virtual destructor */
	~ArcBase() override;

    /** @name Public getters
    * These functions return protected properties either set by the user or inherent to the arc type.*/
    /** @{ */
    /** Ture if object is valid */
    bool IsValid() const {return valid;}
    /** True if subsequent arcs can be drawn beside this.*/
    bool IsParallel() const {return parallel;}
    /** Set the parallel member. Set only during parsing & cleared only during 'join' processing.*/
    void SetParallel(bool p=true) {parallel = p;}
    /** True if subsequent arcs can be drawn overlaid on top of this.*/
    bool IsOverlap() const {return overlap;}
    /** Set the overlap member. Set only during parsing & cleared only during 'join' processing*/
    void SetOverlap(bool o=true) {overlap = o;}
    /** Mark the arc to be joined to the previous one. Used only during parsing */
    void SetJoin(const FileLineColRange &join_position) { join_pos = join_position.start; }
    /** True, if compress mechanism is on foe this arc and can be shifted upwards.*/
    bool IsCompressed() const { return vspacing==DBL_MIN; }
    /** The value of extra space to be added above this arc. Zero if compress is used.*/
    double GetVSpacing() const { return vspacing==DBL_MIN ? 0 : vspacing; }
    /** True if this arc shall be placed at the centerline of a previous one.*/
    bool IsKeepTogether() const {return keep_together;}
    /** If true, do not separate this from following element by automatic pagination */
    bool IsKeepWithNext() const {return keep_with_next;}
    /** If true, do not separate this from previous element during parallel layout*/
    bool IsStickToPrev() const { return stick_to_prev; }
    /** True if a note can be attached to this arc. */
    virtual bool CanBeNoted() const {return false;}
    /** True if the entity is likely to produce height and can be the target of a vertical or a byref symbol. */
    virtual bool CanBeAlignedTo() const { return false; }
    /* True if the entity has a centerline to which markers can be aligned.*/
    virtual bool HasCenterline() const { return false; }
    /** True if this entity aligns to another entity by referring to it.
     * Such entities are laid out independently. */
    virtual bool IsByRef() const { return false; }
    /** For by ref symbols and verticals, it returns the element to
     * align to. For others it is a nullptr.*/
    virtual ArcBase* GetRefTarget() const { return nullptr; }
    /** True if implicitly generated entities (MscChart::AutoGenEntities) shall be placed *after* this element (like titles, etc) */
    virtual bool BeforeAutoGenEntities() const { return false; }
    /** If there is an indicator both before and after this element, we
     * are NOT allowed to merge them. */
    virtual bool SeparatesIndicators() const { return true; }
    /** If this element is followed by a Note, it can store the note for later re-insertion.
     * This feature is useful for entity commands and arrow joins: when a series of
     * instructions are merged and we may want to note only one of them.
     * @returns true if we have stored the Note internally (and took ownership)*/
    virtual bool StoreNoteTemporarily(Note*) { return false; }
    /** After PostParseProcess, we can re-insert the notes stored above.*/
    virtual void ReinsertTmpStoredNotes(ArcList&, ArcList::iterator after [[maybe_unused]]) { };
    /** @} */

    /** @name Geometry & other getters
    * These functions return calcualted parameters of the arc*/
    /** @{ */
    /** Get the Y coordinate of the top of the arc.
     * The actual visual part may not start here,
     * if we have gap proscribed above in 'chart'
     * However, VSpacing is above this position.*/
    double GetFormalPos() const {return yPos;}
    /** Return the height of the element with its side comments -
     * the this may be more than the visual height as the arc may have
     * gap above and/or below proscribed by the 'chart'.*/
    double GetFormalHeight() const {return std::max(height, comment_height);}
    /** Get the Y coordinate range occupied by the arc.
     * This may be wider than the actual visual effect and is used for
     * layout and to gauge associated verticals.*/
    virtual Range GetFormalYExtent() const {return Range(yPos, yPos+GetFormalHeight());}
    /** Get the Y coordinate range actually occupied by visual elements of the arc.
     * This is used to do automatic pagination. */
    virtual Range GetVisualYExtent(bool include_comments) const;
    /** Get an (ordered) list of entities that this arrow or box touches.
      * @return the direction of the arrows inside (left is all arrows are left; bidir if mixed.*/
    virtual EDirType GetToucedEntities(NEntityList &) const {return EDirType::INDETERMINATE;}
    /** Expands our contour calculated as `a` to a cover used for compress bump checking*/
    Area GetCover4Compress(const Area &a) const;
    /** Allocates and fills in a signature object identifying the arc. nullptr if no signature available.*/
    virtual const BoxSignature* GetSignature() const {return nullptr;}
    /** @} */

    /** @name Attribute related functions
    * These functions set attributes or return what kind of attributes we have with what possible values*/
    /** @{ */
    /** Applies a set of attributes to us.*/
    virtual void AddAttributeList(gsl::owner<AttributeList*>);
    /** Applies one attribute to us */
    bool AddAttribute(const Attribute &) override;
    /** Add attribute names and helpers to csh for hinting */
    static void AttributeNames(Csh &csh);
    /** Add attribute values and helpers to csh for hinting */
    static bool AttributeValues(std::string_view attr, Csh &csh);
    /** @} */

    /** @name Joining oriented functions
    * These functions test if elements can be joined and help achieving that*/
    /** @{ */
    /** Returns if this element can be part of a join series at all.
     * If not its join related functions will not be called. */
    virtual bool CanJoin() const { return false; }
    /** Determines what to do if this element is part of a join series and
     * disappears due to entity collapse. If false, we keep any indicators
     * this->PostParseProcess() returns, else we drop them.
     * If not its join related functions will not be called. */
    virtual bool RemoveIndicator() const { return true; }
    /** Get the src or dst end of an element for the purpose of joining.
     * If this is called before PostParseProcess(), it shall return a non-group EntityRef from
     * AllEntities and the actual offset may be imprecise. If it is called after Width()
     * it shall return an EntityRef from ActiveEntities and offset shall be final.
     * Only for elements that can take part in join series'*/
    virtual EndConnection GetConnection(bool is_src [[maybe_unused]]) const { _ASSERT(0); return EndConnection(); } //This entity cannot participate in join series - should not have been called
    /** Get the actual position of the side of an element for the purpose comparing
     * ordering for joining and of setting unspecified ends of an arrow that joins to it.
     * If this is called before PostParseProcess(), it shall return a non-group EntityRef from
     * AllEntities and the actual offset may be imprecise. If it is called after Width()
     * it shall return an EntityRef from ActiveEntities and offset shall be final.
     * Not called for unspecified ends.
     * Only for elements that can take part in join series'*/
    virtual ArrowEnding GetSidePos(bool is_src [[maybe_unused]]) const { _ASSERT(0); return ArrowEnding(); } //This entity cannot participate in join series - should not have been called
    /** Tests if we can set the ending so and/or set an end to a position for the purpose of joining.
     * It is called twice for and only for unspecified endings. Once before PostParseProcess(), with an
     * test_only==true. Then it may be called again before Width() with test_only==false.
     * Changes shall be made only at the second call.
     * Only for elements that can take part in join series'*/
    virtual bool SetEndingDueToJoin(bool is_src [[maybe_unused]], const ArrowEnding&, bool test_only [[maybe_unused]]) { _ASSERT(0); return false; } //This entity cannot participate in join series - should not have been called
    /** Makes an arrow curly join to another one. This is called after PostParseProcess(),
     * when we know that the previous arrow do not disappear and a curly join is still needed.
     * Called only for elements that return can_curly_join==true in GetConnection()*/
    virtual void SetCurlyJoin(bool is_src [[maybe_unused]], ArcBase*, bool src_of_prev [[maybe_unused]]) { _ASSERT(0); } //This entity cannot participate in join series - should not have been called
    /** Which side of the element do we query the centerline? */
    enum class CenterlineSelector
    {
        AT_SRC,     ///<At the source of the arrow
        AT_DST,     ///<At the destination of the arrow
        LEFT_SIDE,  ///<At the phyiscal left side (can be source or dest, if those are meaningful)
        RIGHT_SIDE  ///<At the phyiscal right side (can be source or dest, if those are meaningful)
    };
    /** Get the y position of the end, for the purposes of joining to it or to align a centerline.
    * Elements that CanJoin() or HasCenterline() shall support this.
    * Shall be called only after Layout()*/
    virtual double GetCenterline(CenterlineSelector where [[maybe_unused]]) const { _ASSERT(0); return 0; } //This entity cannot participate in join series - should not have been called
    /** Get the activation status of the entity at the dst end of the arrow,
     * for the purposes of joining to it. Shall be called only after Layout(). */
    virtual double GetActSize(bool at_src [[maybe_unused]]) const { _ASSERT(0); return 0; } //This entity cannot participate in join series - should not have been called
    /** @} */

    /** @name Recursive processors
     * These functions are called recursively for all arcs in this order*/
    /** @{ */
    /** Calculate things  after parsing.
     * Called after parsing and adding attributes.
     * Entity order and collapse/expand is already known here
     * See documentation for libmscgen for more info.*/
    virtual ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                                      Numbering &number, MscElement **note_target, ArcBase *vertical_target);
    /** Substitute name references in labels & process all escapes
     * See documentation for libmscgen for more info.*/
    virtual void FinalizeLabels(Canvas &) {}
    /** Collects vertical distances for hscale=auto mechanism.
     * See documentation for libmscgen for more info.*/
    virtual void Width(Canvas &/*canvas*/, DistanceRequirements &/*vdist*/) {}
    /** Calculate entity layout at y=0 vertical position.
     * Calculates the height, and sets up the area at yPos==0,
     * add its cover to use at placement later to `cover`.
     * Cover or area does not include any spacing left around such as chart->emphVGapAbove.
     * This function is not called for by reference verticals, symbol, nor for
     * notes and comments.
     * See documentation for libmscgen for more info.*/
    virtual void Layout(Canvas& canvas, AreaList* cover);
    /** Calculate entity layout in relation to the coverage of the referred entity. 
     * This function is called only for by reference verticals and symbols.*/
    virtual bool LayoutByRef(Canvas& /*canvas*/, const AreaList& /*target_cover*/, AreaList* /*cover*/) { _ASSERT(0); return false; }
    /** After being placed below the element above us, here we can make final adjustments to layout.
     * This will be called before placing the next element below us.
     * 'cover' contains any symbols drawn in reference to us.
     * Our yPos member is still not at its final coordinate.
     * (This is used for curly joins to add the 'curl'.)*/
    virtual void FinalizeLayout(Canvas &/*canvas*/, AreaList * /*cover*/) {}
    /** Move the arc up or down. This can be called multiple times. */
    void ShiftBy(double y) override {if (valid) {MscElement::ShiftBy(y);}}
    /** Collect the y position of page breaks into MscChart::pageBreakData.
     * See documentation for libmscgen for more info.*/
    virtual void CollectPageBreak() {}
    /** Split the element into two by a page break.
     * Maintain running_state of entities.
     * @returns The amount the element has grown. -1 if we cannot rearrange, -2 if we are to ignore.
     * See documentation for libmscgen for more info.*/
    virtual double SplitByPageBreak(Canvas &/*canvas*/, double /*netPrevPageSize*/,
                                    double /*pageBreak*/, bool &/*addCommandNewpage*/,
                                    bool /*addHeading*/, ArcList &/*res*/) {return -1;}
    /** Place verticals.
     * All height & pos info final by now, except on verticals & notes
     * See documentation for libmscgen for more info.*/
    virtual void PlaceWithMarkers(Canvas &/*canvas*/) {}
    /** Emit warnings that need final layout.
     * See documentation for libmscgen for more info.*/
    virtual void PostPosProcess(Canvas &, Chart *ch) override;
    /** Draw the arc.
     * See documentation for libmscgen for more info.*/
    virtual void Draw(Canvas &canvas, EMscDrawPass pass) const = 0;
    /** @} */
};

/** A list of ArcBase pointers*/
typedef UPtrList<ArcBase> ArcList;


/** An arc representing a small indicator symbol.
 * This arc cannot be generated by the user..
 * it is generated by libmscgen to replace arcs that get hidden due to collapsing
 * a group entity.
 * An indicator is either on a specific entity or in a box, which may have two entities
 * setting its location. `src` and `dst` members capture the horizontal location.*/
class Indicator : public ArcBase
{
    const MscStyleCoW style; ///<Our visual style
    EntityRef src;           ///<Our leftmost entity. Shall always point to ActiveEntities
    EntityRef dst;           ///<Our rightmost entity. Shall always point to ActiveEntities
public:
    /** Creates an indicator object setting its location (`src` and `dst`) to null.*/
    Indicator(MscChart *chart, const MscStyleCoW &st, const FileLineColRange &l);
    /** Creates an indicator object at Entity `s`*/
    Indicator(MscChart *chart, EntityRef s, const MscStyleCoW &st, const FileLineColRange &l);
    bool CanBeAlignedTo() const override { return true; }
    bool HasCenterline() const override { return true; }
    /** True if `src` and `dst` are properly set.*/
    bool IsComplete() const;
    /** Set which entities the iterator is between. */
    void SetEntities(EntityRef s, EntityRef d) {src=s; dst=d;}
    /** Expand the `file_pos` member with that of another interator if at same location.
     * @returns false, if `src` or `dst` differ (and then no change to `file_pos`).*/
    bool Combine(const Indicator *o);
    /** Expand the `file_pos` member with that of another hidden element. */
    void ExtendFilePos(const ArcBase* o);
    /** Returns the x coordinate in chart space of the middle of the indicator */
    double GetXCoord() const;
    EDirType GetToucedEntities(class NEntityList &el) const override;

    bool CanJoin() const override { return true; }
    bool RemoveIndicator() const override { return false; }
    EndConnection GetConnection(bool is_src) const override;
    ArrowEnding GetSidePos(bool is_src) const override;
    bool SetEndingDueToJoin(bool /*is_src*/, const ArrowEnding&, bool /*test_only*/)  override { _ASSERT(0); return false; } //Never unspecified
    void SetCurlyJoin(bool /*is_src*/, ArcBase*, bool /*src_of_prev*/) override { _ASSERT(0); } //never curly
    double GetCenterline(CenterlineSelector /*where*/) const override { return GetIndicatorCover(XY(GetXCoord(), yPos)).y.MidPoint(); }
    double GetActSize(bool at_src) const override;

    void Width(Canvas &canvas, DistanceRequirements &vdist) override;
    void Layout(Canvas &canvas, AreaList *cover) override;
    void Draw(Canvas &canvas, EMscDrawPass pass) const override;
};

/** Root class for all arcs having a label.
 * Contains style, label and numbering fields.*/
class LabelledArc : public ArcBase
{
protected:
    string         label;           ///<The label as the user specified it. Empty if none.
    FileLineCol    linenum_label;   ///<The position of the label in the input file.
    Label          parsed_label;    ///<The label processed and parsed into lines. Set in PostParseProcess()
    StringFormat   basic_format;    ///<The formatting at the start of the label. Set in PostParseProcess(), used in FinalizeLabels()
    int            concrete_number; ///<Negative, if the user specified no specific value for the number (no numbering or automatic). Else the number specified.
    string         url;             ///<The value of the 'url' attribute, empty if none.
    FileLineCol    linenum_url_attr;///<The location of the 'url' attribute, if any.
    string         id;              ///<The value of the 'id' attribute;
    string         idurl;           ///<The value of the 'idurl' attribute;
    FileLineCol    linenum_idurl_attr;///<The location of the 'idurl' attribute, if any.
    MscStyleCoW    style;           ///<The style of the arc. `numbering` and `compress` fields of style are not used. The ArcBase member fields are used instead.
    NumberingStyle numberingStyle;  ///<The numbering style to use. (This is not part of Styles in general, but is a property of contexts. This is a snapshot at the location of the arc.
    string         number_text;     ///<The formatted number assigned to this arc (used by references and notes/comments). Set in PostParseProcess()
    Range          entityLineRange; ///<The y range to apply any vFill or vLine attributes
public:
    LabelledArc(EArcCategory c, MscChart *msc);
    LabelledArc(EArcCategory c, const LabelledArc &al);
    /** Return the style of the arc. */
    const MscStyleCoW &GetStyle() const {return style;}
    bool CanBeNoted() const override { return true; }
    bool CanBeAlignedTo() const override { return true; }
    /** Set style to this name, but combine it with default text style */
    void SetStyleWithText(const char *style_name, const MscStyleCoW *refinement);
    /** Set style to this name, but combine it with default text style. Use existing style if nullptr.*/
    void SetStyleWithText(const MscStyleCoW *style_to_use, const MscStyleCoW *refinement);
    /** Generate a warning on overflown labels.
     * Shall be called from Layout().
     * @param [in] overflow The amount of overflow.
     * @param [in] msg The message to display. nullptr if the space depends on entities and a
     *                 corresponding message shall be displayed.
     * @param [in] e1 The left entity governing the space available for the label.
     * @param [in] e2 The right entity governing the space available for the label. */
    void OverflowWarning(double overflow, const string &msg,
                         CEntityRef e1, CEntityRef e2);
    /** Counts non-word-wrapped labels in the chart and also how many has been overflown.
     * It is used to display a warning at the end if may labels were
     * overflown. Shall be called from Layout();
     * @param [in] space Says how much vertical space the label has.
     *                   If the label is wider, we mark it as overflown,
     *                   but only if no word_wrapping is used for the labels. */
    void CountOverflow(double space);
    /** This function is called before applying attributes. You can apply arc and refinement styles in this function.
     * The attribute list is also offered for picking out the most important attributes. You can ignore it,
     * attributes will be applied later. If you reflect one, pls remove from the list.*/
    virtual void SetStyleBeforeAttributes(AttributeList *l) = 0;
    FileLineCol AddAttributeListStep1(AttributeList *);
    void AddAttributeListStep2(const FileLineCol &label_pos);
    void AddAttributeList(gsl::owner<AttributeList*> l) override {SetStyleBeforeAttributes(l); AddAttributeListStep2(AddAttributeListStep1(l)); }
    bool AddAttribute(const Attribute &) override;
    static void AttributeNames(Csh &csh);
    static bool AttributeValues(std::string_view attr, Csh &csh);
    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                              Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;
    /** Return the formatted number of the arc (empty string if none).*/
    const string &GetNumberText() const {return number_text;}
    /** Return the numbering style of the arc (even if numbering is turned off).*/
    const NumberingStyle &GetNumberingStyle() const {return numberingStyle;}
    void FinalizeLabels(Canvas &canvas) override;
    void PostPosProcess(Canvas &, Chart *ch) override;
    void DrawLSym(Canvas &canvas, const XY &C, XY size, const LineAttr &line) const;
};

/** Holds a horizontal position, as specified by the user.
* (Apologies for the name.)
* Can encode the following language fragments:
* 1. AT @<entity> [@<offset>]
* 2. AT @<entity> - @<entity> [@<offset>]
* 3. AT @<entity> -|--|+|++ [@<offset>]
*
* The first will result in type POS_AT, the second in POS_CENTER, while the third in
* POS_LEFT_SIDE, POS_LEFT_BY, POS_RIGHT_SIDE or POS_RIGHT_BY, resp. */
struct VertXPos
{
    /** AT is at the entity
    ** CENTER is between the two entities
    ** BY is immediately at the line, but not overlapping it
    ** SIDE is immediately at the line, arrow tips overlap the line */
    /** Options for horizontal position in relation to one or two entities */
    enum EPosType
    {
        POS_AT,         ///<At the entity
        POS_CENTER,     ///<Halfway between two entities
        POS_THIRD_LEFT, ///<one third towards the neighbour on the left of an entity
        POS_THIRD_RIGHT,///<one third towards the neighbour on the right of an entity
        POS_LEFT_BY,    ///<Immediately left by the entity line, but not overlapping it.
        POS_LEFT_SIDE,  ///<immediately left by the line, arrow tips overlap the line.
        POS_RIGHT_BY,   ///<Immediately right by the entity line, but not overlapping it.
        POS_RIGHT_SIDE  ///<immediately right by the line, arrow tips overlap the line.
    };

    static bool IsOneEntityPos(EPosType t) { return t==POS_AT || t==POS_LEFT_BY || t==POS_RIGHT_BY || t==POS_LEFT_SIDE || t==POS_RIGHT_SIDE || t==POS_THIRD_LEFT || t==POS_THIRD_RIGHT;  }
    static bool IsTwoEntityPos(EPosType t) { return t==POS_CENTER; }

    bool valid = false;          ///<True if the position is valid
    EPosType pos{};              ///<The type of the position
    EntityRef entity1 = nullptr; ///<The first (or only one) entity
    EntityRef entity2 = nullptr; ///<The second entity, used only for POS_CENTER
    FileLineColRange e1line;     ///<Location of the mention of the first entity in the input line (inside this vertpos fragment)
    FileLineColRange e2line;     ///<Location of the mention of the second entity in the input line (inside this vertpos fragment)
    double offset = 0;           ///<An arbitrary user specified horizontal offset on top of the position defined by `pos`

    /** Create (yet) invalid object */
    VertXPos() noexcept = default;
    /** Create a position with 2 entities (always POS_CENTER*) */
    VertXPos(MscChart& m, std::string_view e1, const FileLineColRange& e1l, std::string_view e2, const FileLineColRange& e2l, double off = 0);
    /** Create a position with one entity (must not be POS_CENTER) */
    VertXPos(MscChart& m, std::string_view e1, const FileLineColRange& e1l, EPosType p = POS_AT, double off = 0);
    /** Calculate the x coordinate of the middle of the element we specify the location for (only after entities are placed)
    * @param m The chart
    * @param width The width of the object
    * @param gap1 Specifies how much gap between the entity and object for POS_XXX_SIDE. Defaults to `m.hscaleAutoXGap`.
    * @param gap2 Specifies how much extra gap between the entity and the object for POS_XXX_BY compared to POS_XXX_SIDE. Defaults to `m.hscaleAutoXGap`*/
    double CalculatePos(MscChart &m, double width = 0, double gap1 = -1, double gap2 = -1) const;
    /** Returns true if the position is valid and refers to one entity only. */
    bool IsOneEntityPos() const { return IsOneEntityPos(pos); }
    /** Returns true if the position is valid and refers to two entities. */
    bool IsTwoEntityPos() const { return IsTwoEntityPos(pos); }
};

/** By reference verticals has this as position. */
struct VertRefPos
{
    ESide side = ESide::INVALID;  ///<RIGHT for 'rightof', LEFT for 'leftof'
    std::string refname = {};     ///<The name of the referenced element. Empty if the previous one
    FileLineCol refname_loc = {}; ///<The location of the refname (if any)
};

/** The base class for all arrows: Self, Dir, Block and Vertical*/
class Arrow : public LabelledArc
{
protected:
    bool headless_mscgen_arrow;   ///<True if this is an mscgen syntax arc defined without arrowhead via one of (--, ==, .., ::)
    StoredNotes tmp_stored_notes; ///<A set of notes attached to us.
public:
    const bool bidir;           ///<True if the arrow is bidirectional.
    /** This is the typical constructor */
    Arrow(bool b, EArcCategory c, MscChart *msc) : LabelledArc(c, msc), headless_mscgen_arrow(false), bidir(b){}
    /** Constructor used only when converting an Box to and BlockArrow*/
    Arrow(bool b, EArcCategory c, const LabelledArc &al) : LabelledArc(c, al), headless_mscgen_arrow(false), bidir(b) {}
    Arrow(const Arrow&) = delete;
    Arrow(Arrow&&) = default;
    /** Get the refinement style for a given arrow symbol
     * The Arrow:: version returns the style for self and dir arrows, BlockArrow overrides. */
    virtual const MscStyleCoW *GetRefinementStyle4ArrowSymbol(EArcSymbol t) const;
    /** Add a new arrow segment. Called during processing.
     * @param [in] data The type of the new arrow segment, indication of amy
     *                  potential loss indication and its location.
     * @param [in] m The name of the entity (can be nullptr when omitted by user)
     * @param [in] ml The location of the name in the input file.
     * @param [in] l The location of the whole added segment (for error messages)*/
    virtual Arrow *AddSegment(ArrowSegmentData data, std::string_view m, const FileLineColRange &ml,
                              const FileLineColRange& l) = 0;
    /** Add a loss position. */
    virtual Arrow* AddLostPos(gsl::owner<VertXPos*> pos, const FileLineColRange& l) = 0;
    /** Add the starting position. If ending is null, we assume src is
     * LSIDE or RSIDE and make it a special ending (used when user specifies pipe symbol)*/
    virtual Arrow *AddStartPos(ArrowEnding *ending=nullptr) = 0;
    /** Add the ending position. If ending is null, we assume src is
     * LSIDE or RSIDE and make it a special ending (used when user specifies pipe symbol)*/
    virtual Arrow *AddEndPos(ArrowEnding *ending=nullptr) = 0;
    /** Indicate that this arrow was defined via an mscgen headless arrow symbol (--, ==, ., ::)*/
    void Indicate_Mscgen_Headless() { headless_mscgen_arrow = true; }
    bool StoreNoteTemporarily(Note* cn) override;
    void ReinsertTmpStoredNotes(ArcList& list, ArcList::iterator after) override;
    bool AddAttribute(const Attribute &) override;
    static void AttributeNames(Csh &csh);
    static bool AttributeValues(std::string_view attr, Csh &csh);
    virtual void PostPosProcess(Canvas &, Chart *ch) override;
};

/** Describes the position and direction of an arrow ending
 * after PostParseProcess, for the purpose of curly joining arrows.
 * Note that 'entity' can be pointing to LSide or RSide for
 * arrows with unspecified ending (such as a->;) */
struct ArrowEndConnection : public ArrowEnding
{
    bool body_is_left; ///<True if the body of the arrow is left of this position
    ArrowEndConnection(EntityRef e, bool l, double o = 0) : ArrowEnding(e, o), body_is_left(l) {}
    ArrowEndConnection(const ArrowEnding &e, bool l) : ArrowEnding(e), body_is_left(l) {}
    /** Returns true if the two endings are totally equal (even the direction) - then they can be curly joined.*/
    bool CurlyConnectsTo(const ArrowEndConnection &o) const { return entity==o.entity && offset==o.offset && body_is_left==o.body_is_left; }
    /** Compares the position of two endings. */
    bool operator < (const ArrowEndConnection &o) const { return std::tie(entity->pos_exp, offset) < std::tie(o.entity->pos_exp, o.offset); }
};


/** An arrow with the same entity as start and end (like "a->a")*/
class SelfArrow : public Arrow
{
protected:
    EntityRef src;           ///<The entity of the arrow
    double    YSize;         ///<The vertical difference between the start and end of the arrow
    const double XSizeUnit;  ///<This is the width of the arrow arc in entity units (=130 pixel).
    bool user_specified_side;///<True if the side attribute was specified by the user.

    mutable double xPos;   ///<The x position of the arrowheads (activation taken into account)
    mutable double src_height; ///<Calculated start arrowhead size (half height of it)
    mutable double dst_height; ///<Calculated end arrowhead size (half height of it)
    mutable XY wh;         ///<Calculated width and height of the arrow arc
    mutable double sx;     ///<Calculated left side of label
    mutable double dx;     ///<Calculated right side of label
    mutable double src_act;///<Activation offset of entity 'src' at the arrow. 0 if not active.
    mutable Contour clip_area;///<Used to mask out the line at arrowheads
public:
    const EArcSymbol type; ///<What kind of arrow symbol was used to define this arrow.
    /** Standard constructor. 'ys' shall be the height of the drawn arrow. */
    SelfArrow(EArcSymbol t, std::string_view s, const FileLineColRange &sl,
                 MscChart *msc, double ys);
    bool HasCenterline() const override { return true; }
    Arrow *AddSegment(ArrowSegmentData data, std::string_view m, const FileLineColRange &ml,
                      const FileLineColRange &l) override;
    Arrow *AddLostPos(gsl::owner<VertXPos*> pos, const FileLineColRange &l) override;
    Arrow *AddStartPos(ArrowEnding *ending = nullptr) override;
    Arrow *AddEndPos(ArrowEnding *ending = nullptr) override;
    bool AddAttribute(const Attribute &) override;
    EDirType GetToucedEntities(NEntityList &el) const override;
    void SetStyleBeforeAttributes(AttributeList *l) override;
    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                              Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;

    bool CanJoin() const override { return true; }
    EndConnection GetConnection(bool is_src) const override;
    ArrowEnding GetSidePos(bool /*is_src*/) const override { return ArrowEnding(src, 0); }  //Before PostParseProcess() this is an iterator to AllEntities, after to ActiveEntities - OK
    bool SetEndingDueToJoin(bool /*is_src*/, const ArrowEnding&, bool /*test_only*/) override { _ASSERT(0); return false; } //Never unspecified
    void SetCurlyJoin(bool /*is_src*/, ArcBase*, bool /*src_of_prev*/) override { _ASSERT(0); } //never curly
    double GetCenterline(CenterlineSelector where) const override { return yPos + (where==CenterlineSelector::AT_SRC ? src_height : where==CenterlineSelector::AT_DST ? src_height+wh.y : src_height+wh.y/2); }
    double GetActSize(bool /*at_src*/) const override { return src_act; }

    void Width(Canvas &canvas, DistanceRequirements &vdist) override;
    void Layout(Canvas &canvas, AreaList *cover) override;
    void ShiftBy(double y) override { if (valid) { clip_area.Shift(XY(0, y));  MscElement::ShiftBy(y); } }
    void PostPosProcess(Canvas &, Chart *ch) override;
    void RegisterLabels() override;
    void CollectIsMapElements(Canvas &canvas) override;
    void Draw(Canvas &canvas, EMscDrawPass pass) const override;
};

class BoxSeries;

/** A (potentially multi-segmented) arrow.
 * This is the most common chart element.*/
class DirArrow : public Arrow
{
protected:
    ArrowEnding              src;                  ///<The position of the start of the arrow  (specified via regular arrow stuff or via 'start before')
    ArrowEnding              dst;                  ///<The position of the end of the arrow  (specified via regular arrow stuff or via 'end after')
    std::vector<EntityRef>   middle;               ///<An array of mid-stops of the arrow in the order as they appear in the input file
    std::vector<FileLineCol> linenum_middle;       ///<Location of mid-stop entity names in the input file (within this arrow definition)
    std::vector<EArcSymbol>  segment_types;        ///<Types of segments: one for each segment ([0] is the one from src). Set during AddSegment() calls.
    std::vector<LineAttr>    segment_lines;        ///<Line types of segments. Set during AddAttributeList() from `segment_types`.
    const bool               specified_as_forward = false; ///<True if user specified "a->b", false if "b<-a"
    double                   slant_angle = 0;      ///<The angle of the arrow. Taken from the context, may be modified by style and/or attribute.
    double                   slant_depth = 0;      ///<The value of the slant depth, an alternative to the slant arrow. Used only if the user specified the "slant_depth" attr.
    int                      lost_at = -2;         ///<Index of where the message was lost if expressed via an asterisk: -2=none, -1=src, 0..n = at a midpoint, n=dst; where n is the size of 'middle'.
    bool                     lost_is_forward = true;///<True if the loss was at the fw side of 'lost_at' entity, false if at the backwards size
    FileLineCol              linenum_asterisk;     ///<The line and column number of the asterisk marking the loss. Invalid if none.
    VertXPos                 lost_pos;             ///<The position of the loss symbol
    FileLineCol              linenum_lost_at;      ///<The line and column number of the 'lost at' clause marking the loss. Invalid if none.
    constexpr static double  lsym_side_by_offset= 0.5;  ///<if 'lost at a++' or 'lost at a--' is specified, we add the size of the loss symbol times this much extra offset compared to 'lost at a+/-'
    ArcBase                 *curly_join_to = nullptr;///<Set to the arrow above us, we curly join to. nullptr if none.
    bool                     curly_join_to_src = false;    ///<True if we curly join to the 'src' end of 'curly_join_to'
    std::pair<bool, bool>    user_unspecified_ends = {false, false};///<True if the user originally left src and/or dst unspecified. We may set src/dst in SetEndingDueToJoin(), but these show if originally we had them unspecified - so that we can update them. Valid after AddAttributeList.

    mutable bool is_forward = false;///<True if the src of the arrow is left of the dst (considering *_ending and *_to_box). Valid only after parsing (specifically after AddAttributeList()). (Before that we have no entity ordering yet.)
    mutable double sin_slant = 0; ///<Pre-calculated value of `slant_angle`
    mutable double cos_slant = 0; ///<Pre-calculated value of `slant_angle`
    mutable double sx = 0;        ///<Calculated value of the start arrowhead (sx can be > dx)
    mutable double dx = 0;        ///<Calculated value of the end arrowhead (sx can be > dx)
    mutable double sx_text = 0;   ///<Calculated value of the left of the label (usually sx adjusted by arrowhead)
    mutable double dx_text = 0;   ///<Calculated value of the right of the label (usually dx adjusted by arrowhead)
    mutable double cx_text = 0;   ///<Calculated value of the middle of the label (usually middle of sx and dx)
    mutable double cx_lsym = 0;   ///<Calculated value of the middle of the loss symbol (if any)
    mutable XY lsym_size = {0,0}; ///<Calculates size of the lost symbol (if any loss)
    mutable bool has_skip = false;///<True if any in skip is true (is so, if the arrowhead has a skipType != ARROW_NONE & we actually skip over entities
    mutable std::vector<bool> skip;          ///<True, if this entity is inserted as a skip entity. src and dst are never skip.
    mutable std::vector<double> xPos;        ///<X coordinates of arrowheads (sorted to increase, [0] is for left end of the arrow). Always the middle of the entity line.
    mutable std::vector<double> act_size;    ///<Activation size of the entities (sorted from left to right). ==0 if not active
    mutable std::vector<DoublePair> margins; ///<How much of the arrow line is covered by the arrowhead (sorted left to right), see MscArrowAttr::getWidths()
    mutable Contour text_cover; ///<The cover of the label, so that we can hide entity lines in PostPosProcess() - not used by BlockArrow
    mutable double centerline = 0;///<y offset of line compared to yPos
    mutable Contour clip_area;  ///<Used to mask out the line at arrowheads - not used by BlockArrow.
    mutable double curly_margin = 0;///<The size of the arrowhead at the top of the curly join (if any)
    mutable double curly_radius = 0;///<The radius of a curly join (if any)
    mutable Path src_line;      ///<In case of an arrow with curly join to a previous one this is the curvy join plus the first line segment. In FinalizeLayout().

    void InsertSkipEntity(unsigned u, Entity *pEntity);
    double ConsiderLabelWidth(double width_needed);
public:
    DirArrow(ArrowSegmentData data,
             std::string_view s, const FileLineColRange &sl,
             std::string_view d, const FileLineColRange &dl,
             MscChart *msc, bool fw);
    /** Constructor used while converting a collapsed Box to an BlockArrow*/
    DirArrow(const NEntityList &el, bool bidir, EArcSymbol boxsymbol, const LabelledArc &al);
    bool HasCenterline() const override { return true; }
    Arrow *AddSegment(ArrowSegmentData data, std::string_view m, const FileLineColRange &ml,
                      const FileLineColRange &l) override;
    Arrow *AddLostPos(gsl::owner<VertXPos*> pos, const FileLineColRange &l) override;
    Arrow *AddStartPos(ArrowEnding *ending = nullptr) override;
    Arrow *AddEndPos(ArrowEnding *ending = nullptr) override;
    bool IsMscgenBroadcastArrow() const;
    void SetStyleBeforeAttributes(AttributeList *l) override;
    void AddAttributeList(gsl::owner<AttributeList*> l) override;
    bool AddAttribute(const Attribute &) override;
    EDirType GetToucedEntities(NEntityList &el) const override;
    void ConvertXPosToActiveEntities(VertXPos &pos);

    bool CanJoin() const override { return true; }
    EndConnection GetConnection(bool is_src) const override;
    ArrowEnding GetSidePos(bool is_src) const override { return is_src ? src : dst; } //Before PostParseProcess() this is an iterator to AllEntities, after to ActiveEntities - OK
    bool SetEndingDueToJoin(bool is_src, const ArrowEnding &ending, bool test_only) override;
    void SetCurlyJoin(bool is_src, ArcBase *prev, bool src_of_prev) override;
    double GetCenterline(CenterlineSelector where) const override { return yPos+GetCenterlineAtXPos(where==CenterlineSelector::AT_SRC ? sx : where == CenterlineSelector::AT_DST ? dx : (where == CenterlineSelector::LEFT_SIDE)==is_forward ? sx : dx); }
    double GetActSize(bool at_src) const override;

    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                              Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;
    /** Update the stored activation status of entities.
     * Called if a centierlined entity command after us has changed active entities.*/
    virtual void UpdateActiveSizes();
    void FinalizeLabels(Canvas &canvas) override;
    void Width(Canvas &canvas, DistanceRequirements &vdist) override;
    void Layout(Canvas &canvas, AreaList *cover) override;
    void FinalizeLayout(Canvas &canvas, AreaList *cover)  override;
    /** Get the Centerline Delta from the top of the arc at horizontal position `x`.
     * A negative return value indicates the object has no centerline.
     * Shall be called only after Layout()*/
    virtual double GetCenterlineAtXPos(double x) const;
    /** Calculates `area.mainline`. Tricky for slanted arrows.
     * @param [in] thickness The y thinckess of the mainline*/
    void CalculateMainline(double thickness);
    /** Is this the start or end of the arrow?
     * From the index of `xPos` and `specified_as_forward` give EArrowType::{START,MIDDLE,END}*/
    EArrowEnd WhichArrow(unsigned i) const;
    void ShiftBy(double y) override;
    /** Check if the entities in `src` - `middle[]` - `dst` are all left-to-right or right-to-left*/
    void CheckSegmentOrder(double y);
    virtual void PostPosProcess(Canvas &, Chart *ch) override;
    void RegisterLabels() override;
    void CollectIsMapElements(Canvas &canvas) override;
    void AddClippedLine(Canvas& canvas, Path path, const LineAttr& line,
                        const MscArrowHeads& loc_arrow, unsigned i, const Block* clip_by) const;
    void DrawArrow(Canvas &canvas, const Label &loc_parsed_label,
                   const std::vector<LineAttr>& loc_segment_lines,
                   const MscArrowHeads &loc_arrow, const Block* clip_by) const;
    void Draw(Canvas &canvas, EMscDrawPass pass) const override;
};

/** A block arrow */
class BlockArrow : public DirArrow
{
protected:
    /** Signature of the Box, if we got created as a collapsed Box. nullptr if not*/
    const BoxSignature * const sig;

    mutable double sy;      ///<The y coordinate of the top of the arrow body (not including arrowheads). Set in Width()
    mutable double dy;      ///<The y coordinate of the bottom of the arrow body (not including arrowheads). Set in Width()
    mutable double ind_off; ///<If we draw an indicator inside, this is its y offset from top of text
    mutable unsigned stext; ///<Index of the left entity of the segment containing the text: xPos[stext]. Set in Width()
    mutable unsigned dtext; ///<Index of the right entity of the segment containing the text: xPos[stext]. Set in Width()
    mutable double sm;      ///<Left marging for text. Set in Width()
    mutable double dm;      ///<Right marging for text. Set in Width()
    mutable std::vector<Contour> outer_contours; ///<The outline for each segment
public:
    /** Upgrade an DirArrow to a block arrow: the regular way of creating one. */
    BlockArrow(DirArrow&&);
    /** Used when converting a collapsed Box to an BlockArrow */
    BlockArrow(const NEntityList &, bool bidir, EArcSymbol boxsymbol, const LabelledArc &al, const BoxSignature *s);
    ~BlockArrow() override;
    bool StoreNoteTemporarily(Note*) override { return false; }
    void SetTemporarilyStoredNotes(StoredNotes&&); ///<Used by Box::PostParseProcess(), when collapsing to block arrow.
    void SetStyleBeforeAttributes(AttributeList *l) override;
    const MscStyleCoW *GetRefinementStyle4ArrowSymbol(EArcSymbol t) const override;
    const BoxSignature* GetSignature() const override { return sig; }
    static void AttributeNames(Csh &csh);
    static bool AttributeValues(std::string_view attr, Csh &csh);

    EndConnection GetConnection(bool is_src) const override { auto ret = DirArrow::GetConnection(is_src); ret.can_curly_join = false; return ret; }
    void SetCurlyJoin(bool /*is_src*/, ArcBase*, bool /*src_of_prev*/) override { _ASSERT(0); } //We cannot curly join

    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                              Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;
    void FinalizeLabels(Canvas &canvas) override;
    void Width(Canvas &canvas, DistanceRequirements &vdist) override;
    void Layout(Canvas &canvas, AreaList *cover) override;
    Range GetVisualYExtent(bool include_comments) const override;
    void ShiftBy(double y) override;
    virtual void PostPosProcess(Canvas &, Chart *ch) override;
    void RegisterLabels() override;
    void CollectIsMapElements(Canvas &canvas) override;
    void Draw(Canvas &canvas, EMscDrawPass pass) const override;
};

/** A vertical */
class Vertical : public Arrow
{
public:
    /** Lists Vertical shapes */
    enum EVerticalShape {
        ARROW,    ///<A block arrow
        BOX,      ///<A box
        BRACE,    ///<A curly brace
        BRACKET,  ///<A square bracket (potentially with custom corners)
        RANGE,    ///<A range with potential arrow
        POINTER,  ///<An arrow that looks like a pointer to self
    };
protected:
    const EArcSymbol type;   ///<Tells us what kind of symbol was used to define us (-> or -- etc)
    string src;              ///<The top marker (if any)
    string dst;              ///<The bottom marker (if any)
    const bool lost;         ///<Whether we had a loss symbol or not
    const FileLineCol lost_pos; ///<The position of the loss symbol (if any)
    EVerticalShape shape;    ///<The shape of the vertical
    VertXPos pos;            ///<The horizontal position, specified by the user
    WidthAttr width_attr;    ///<The value of the width attribute
    VertRefPos by_ref;       ///<The position specified by the user, if we are a by ref vertical (no markers).
    ArcBase *ref_target;     ///<The arc before us. Used only if no markers specified.
    mutable bool forward;    ///<if the arrow is up to down. For byref verticals we set it already in the ctor.
    mutable bool left;       ///<True if we are at the left side of the entity (or we draw such)
    mutable double width;    ///<width of us (just the body for arrows)
    mutable double ext_width;///<Extra width of the arrowhead
    mutable double radius;   ///<Calculated radius (important for brace, where height may limit)
    mutable XY lsym_size;     ///<Calculates size of the lost symbol (if any loss)
    mutable std::vector<double> ypos; ///<A two element array with the y pos of the start and end. Set in PlaceWithMarkers()
    mutable double s_text;   ///<Left/Top/Bottom edge of text for side==END/LEFT/RIGHT. An y coordinate for LEFT/RIGHT, an x for END
    mutable double d_text;   ///<Right/Bottom/Top edge of text for side==END/LEFT/RIGHT. An y coordinate for LEFT/RIGHT, an x for END
    mutable double t_text;   ///<Top/Right/Left edge of text for side==END/LEFT/RIGHT. An x coordinate for LEFT/RIGHT, an y for END
    mutable double xpos;     ///<x coordinate of middle (ARROW_OR_BOX) or the key line (BRACE, BRACKET, RANGE)
    mutable double vline_off;///<The distance of the edge of the construct facing the entity line from the main vertical line (signed value), only used for brace/pointer/range/bracket
    mutable Block clip_block;///<The clip of the line for brackets and pointers, ignoring arrowheads (for PPT)
    mutable Contour clip_line;  ///<The clip of the line for brackets and pointers
    mutable std::vector<Contour> outer_contours; ///<Calculated contour (only one element)
    void DoLayout(Canvas& canvas, const AreaList* target_cover=nullptr);
public:
    bool layout_warning = true;///<If set, we emit layout warnings if the vertical is attached to a ParallelBlock with one_by_one_merge and spans _above_ the ParallelBlock 
    /** Constructor for arrow or box symbols with two marker names */
    Vertical(const ArcTypePlusDir *t, std::string_view s, std::string_view d, MscChart *msc);
    /** Constructor for a by reference vertical */
    Vertical(const ArcTypePlusDir* t, gsl::owner<VertRefPos*> refpos, MscChart* msc);
    Arrow *AddSegment(ArrowSegmentData data, std::string_view m, const FileLineColRange &ml,
                      const FileLineColRange &l) override;
    Arrow *AddLostPos(gsl::owner<VertXPos*> pos, const FileLineColRange &l) override;
    Arrow *AddStartPos(ArrowEnding *ending = nullptr) override;
    Arrow *AddEndPos(ArrowEnding *ending = nullptr) override;
    /** Add the parsed horizontal position (starting with the AT keyword) */
    Vertical* AddXpos(VertXPos *p);
    /** Add the parsed reference (starting with the RIGHTOF or LEFTOF keywords) */
    Vertical* AddRefPos(VertRefPos* p);
    /** Sets the shape of the vertical. Used only in parsing.*/
    void SetVerticalShape(EVerticalShape sh);
    bool CanBeNoted() const override { return true; }
    bool CanBeAlignedTo() const override { return false; }
    bool StoreNoteTemporarily(Note*) override { return false; }
    void SetStyleBeforeAttributes(AttributeList *l) override;
    const MscStyleCoW *GetMyRefinementStyle() const;
    bool AddAttribute(const Attribute &) override;
    static void AttributeNames(Csh &csh);
    static bool AttributeValues(std::string_view attr, Csh &csh, EVerticalShape shape);
    bool IsByRef() const override;
    ArcBase* GetRefTarget() const override { return IsByRef() ? ref_target : nullptr; }
    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                              Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;
    void Width(Canvas &canvas, DistanceRequirements &vdist) override;
    void Layout(Canvas &canvas, AreaList *cover) override;
    bool LayoutByRef(Canvas& canvas, const AreaList& target_cover, AreaList* cover) override;
    Range GetVisualYExtent(bool include_comments) const override;
    void ShiftBy(double y) override;
    Range GetFormalYExtent() const override { return Range(false); }
    double SplitByPageBreak(Canvas &/*canvas*/, double /*netPrevPageSize*/,
                            double /*pageBreak*/, bool &/*addCommandNewpage*/,
                            bool /*addHeading*/, ArcList &/*res*/) override {return -2;}
    void PlaceWithMarkers(Canvas& canvas) override { if (!GetRefTarget()) DoLayout(canvas); }
    virtual void PostPosProcess(Canvas &, Chart *ch) override;
    void RegisterLabels() override;
    void CollectIsMapElements(Canvas &canvas) override;
    void DrawBraceLostPointer(Canvas &canvas, const LineAttr &line, const MscArrowHeads &arrow,
                              const Contour *clip_by = nullptr, bool start = true) const;
    void Draw(Canvas &canvas, EMscDrawPass pass) const override;
};

} //msc namespace

#endif //ARCS_H