/*
    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 commands.h The declaration of classes for commands, entities, etc.
 * @ingroup libmscgen_files */

#if !defined(COMMANDS_H)
#define COMMANDS_H

#include "boxes.h" //includes also arcs.h

namespace msc {

/** The base class for commands.
 * Represents an invisible (not drawing anything), thin line of zero height in itself.
 * Child classes add various functions (new page, new background, etc.)
 * Some child classes also add visible effects, such as entity headings. */
class Command : public ArcBase
{
public:
    /** Default constructor */
    Command(EArcCategory c, MscChart* msc) : ArcBase(c, msc) {};
    bool AddAttribute(const Attribute&) override { return false; }
    void Layout(Canvas& canvas, AreaList* cover) override;
    void Draw(Canvas&/*canvas*/, EMscDrawPass /*pass*/) const override = 0;
};

/** The command for entity definition and manipulation.
 * Any mention of an entity results in a EntityCommand, along with the 'heading' command.
 * Subsequent EntityCommand elements are merged in MscChart::PostParseProcess() into one.
 * EntityCommand elements are kept in the ArcLists whenever the status (or style)
 * of an entity changes and/or when an entity heading needs to be drawn.
 * This class contains a set of EntityApp elements that can be tracked by themselves. */
class EntityCommand : public Command
{
protected:
    EntityAppList entities;          ///<The list of EntityApp this command includes
    StoredNotes   tmp_stored_notes;  ///<A set of notes attached to one of the entities in 'entities'
    std::string   target_entity;     ///<Subsequent note commands shall target this entity (present in `entities`)
public:
    const bool    internally_defined;///<True if generated by an lcomment.* command: will not merge with other CommandEntities
    EntityCommand(gsl::owner<EntityAppHelper*> e, MscChart* msc, bool in);
    EntityCommand(MscChart* chart, std::string_view command, const FileLineColRange& l);
    bool CanBeNoted() const override { return !internally_defined; }
    bool CanBeAlignedTo() const override { return !internally_defined; }
    bool HasCenterline() const override { return true; }
    bool SeparatesIndicators() const override { return std::ranges::any_of(entities, [](const std::unique_ptr<EntityApp>& pEntityApp) { return pEntityApp->draw_heading; }); }
    void AttachComment(Note*) override;
    bool StoreNoteTemporarily(Note*) override;
    void ReinsertTmpStoredNotes(ArcList& list, ArcList::iterator after) override;
    void MoveMyContentAfter(EntityAppHelper& e);
    void AppendToEntities(EntityAppList&& e);
    void Combine(EntityCommand* ce);
    bool AddAttribute(const Attribute&) override;
    EntityCommand* ApplyPrefix(std::string_view prefix);
    static void AttributeNames(Csh& csh);
    static bool AttributeValues(std::string_view attr, Csh& csh);
    EntityApp* FindAddEntityDefForEntity(const string& entity, const FileLineColRange& l);
    EEntityStatus GetCombinedStatus(const Entity& entity) const;
    double GetCenterline(CenterlineSelector /*where*/) const override { return GetVisualYExtent(false).MidPoint(); }
    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 LayoutCommentsHelper(Canvas& canvas, AreaList* cover, double& l, double& r) override;
    void Layout(Canvas& canvas, AreaList* cover) override;
    Range GetVisualYExtent(bool include_comments) const override;

    void ShiftBy(double y) override;
    double SplitByPageBreak(Canvas&/*canvas*/, double /*netPrevPageSize*/,
                            double /*pageBreak*/, bool&/*addCommandNewpage*/,
                            bool /*addHeading*/, ArcList&/*res*/) override;
    virtual void PostPosProcess(Canvas&, Chart* ch) override;
    void RegisterLabels() override;
    void CollectIsMapElements(Canvas& canvas) override;
    void RegisterCover(EMscDrawPass pass) const override;
    void Draw(Canvas& canvas, EMscDrawPass pass) const override;
};

/** The object representing the `newpage` command or an automatic page break*/
class Newpage : public Command
{
    bool auto_heading_attr; ///<True if we need to generate an automatic heading
    std::unique_ptr<EntityCommand>
        autoHeading;        ///<The automatically inserted heading that follows, nullptr if none
public:
    const bool manual;                ///<True if this was inserted by the user manually via the `newpage` command
    Newpage(MscChart* msc, bool m);
    bool AddAttribute(const Attribute& a) 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;
    void FinalizeLabels(Canvas& canvas) override;
    void Width(Canvas& canvas, DistanceRequirements& vdist) override;
    void Layout(Canvas& canvas, AreaList* cover) override;
    void ShiftBy(double y) override;
    void CollectPageBreak() override;
    void PlaceWithMarkers(Canvas& cover) override;
    virtual void PostPosProcess(Canvas&, Chart* ch) override;
    void Draw(Canvas& canvas, EMscDrawPass pass) const override;
};

/** The object representing the background.* = chart option*/
class SetBackground : public Command
{
public:
    FillAttr fill; ///<The new fill value for the background

    SetBackground(MscChart* msc, FillAttr f) :
        Command(EArcCategory::TINY_EFFORT, msc),
        fill(f) {}
    virtual void PostPosProcess(Canvas&, Chart* ch) override;
    void Draw(Canvas&/*canvas*/, EMscDrawPass /*pass*/) const override {}
};

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

    SetNumbering(MscChart* msc, size_t len, size_t inc) :
        Command(EArcCategory::TINY_EFFORT, msc),
        set_length(len), inc_by(inc) {AddAttributeList(nullptr);}
    ArcBase* PostParseProcess(Canvas& canvas, bool hide, EntityRef& left, EntityRef& right,
                              Numbering& number, MscElement** note_target, ArcBase* vertical_target) override;
    void Draw(Canvas&/*canvas*/, EMscDrawPass /*pass*/) const override {}
};

/** Represents a `mark` command, defining a vertical position marker.*/
class Marker : public Command
{
protected:
    string name;       ///<The name of the marker
    std::optional<CenterlineSelector> centerline;   ///<True if we mark the centerline of a previous element
    ArcBase* target; ///<The previous element we mark the centerline of
    double     offset; ///<Its potential offset from its declared position of yPos (an attribute)
public:
    Marker(std::string_view m, FileLineColRange ml, MscChart* msc);
    double GetYPos() const { return centerline && target ? target->GetCenterline(*centerline) : yPos + offset; }
    ArcBase* GetTarget() const { return centerline ? target : nullptr; }
    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;
    void Width(Canvas& canvas, DistanceRequirements& vdist) override;
    void ShiftBy(double y) override;
    void Draw(Canvas&/*canvas*/, EMscDrawPass /*pass*/) const override {}
};

/** A temporary class holding two strings of the input file with their location */
class NamePair
{
public:
    string src;             ///<First string
    string dst;             ///<Second string
    FileLineColRange sline; ///<Location of first string
    FileLineColRange dline; ///<Location of second string
    NamePair(std::string_view s, const FileLineColRange& sl,
             std::string_view d, const FileLineColRange& dl) :
        src(s), dst(d), sline(sl), dline(dl) { }
};

/** Represents the `hspace` command*/
class AddHSpace : public Command
{
protected:
    EntityRef src;          ///<First of the two entity in between we add the space
    EntityRef dst;          ///<Second of the two entity in between we add the space
    FileLineColRange sline; ///<Location of the first entity name in the input file (for error msgs)
    FileLineColRange dline; ///<Location of the second entity name in the input file (for error msgs)
    StringFormat format;    ///<Text format to apply, when the space to add is caluclated as the width of a text
    OptAttr<string> label;  ///<Text, the width of which will be used to add space between the two entities (if a text is used)
    OptAttr<double> space;  ///<Number of pixels to add between the entities (if a number was given)
public:
    AddHSpace(MscChart*, const NamePair*);
    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;
    void Width(Canvas& canvas, DistanceRequirements& vdist) override;
    void Draw(Canvas&/*canvas*/, EMscDrawPass /*pass*/) const override {}
};

/** Represents the `vspace` command*/
class AddVSpace : public Command
{
protected:
    StringFormat format;             ///<Text format to apply, when the space to add is caluclated as the height of a text
    OptAttr<string> label;           ///<Text, the height of which will be used to add space (if a text is used)
    OptAttr<double> space;           ///<Number of pixels to add (if a number was given)
    bool compressable;               ///<If true and compress of the below arcs is on, the vertical space is not added (disappears)
public:
    AddVSpace(MscChart*);
    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;
    void Layout(Canvas& canvas, AreaList* cover) override;
    void Draw(Canvas&/*canvas*/, EMscDrawPass /*pass*/) const override {}
};

/** Holds a horizontal position, as specified by the user with a description on how to interpret the position.
 * (Apologies for the name.)
 * Can encode the following language fragments:
 * 1. @<prefix> AT @<entity> [@<offset>]
 * 2. @<prefix> AT @<entity> - @<entity> [@<offset>]
 * 3. @<prefix> AT @<entity> -|--|+|++ [@<offset>]
 *
 * Where @<prefix> can be `center`, `left`, `right` or none.
 * This basically says whether the left or right side or the center of an entity shall be at the
 * location specified after the AT clause.
 */
class ExtVertXPos : public VertXPos
{
public:
    /** This basically says whether the left or right side or the center of an entity shall be at the
     * location specified after the AT clause.*/
    enum ERelativeTo {
        LEFT = 0,   ///<The left side of the object shall be at this position
        CENTER = 1, ///<The center of the object shall be at this position
        RIGHT = 2,  ///<The right side of the object shall be at this position
        NONE = 3,   ///<No sides were specified
        BAD_SIDE  ///<Invalid value
    } side;       ///<Says what part of the object shall be at this location.
    FileLineColRange side_line; ///<The location of the input file where the left/right/center clause was specified
    ExtVertXPos() : side(NONE) {}
    ExtVertXPos(std::string_view s, const FileLineColRange& sl, const VertXPos* p);
    ExtVertXPos(ERelativeTo t, const FileLineColRange& sl, const VertXPos* p);
    ExtVertXPos(const VertXPos* p);
};

/**A catalog of box collapse status */
class InlineSignatureCatalog : public std::map<struct InlineSignature, std::string>
{
public:
    bool Deserialize(std::string_view& s);
    std::string Serialize() const;
};

/** Box attributes defining a box.
 * We save these to determine, if a box previously collapsed on the GUI
 * is the same as a box after re-compilation (and potentially source text modification)*/
struct InlineSignature
{
    FileLineColRange file_pos;  ///<The location of the box in the input file.
    std::string lang;           ///<What is the language of the inlined chart
    size_t content_len;         ///<How many characters there are in the inlined chart
    std::strong_ordering operator <=> (const InlineSignature& o) const noexcept = default;
    InlineSignatureCatalog::iterator WhichIsSimilar(InlineSignatureCatalog& cat) const;
    bool Deserialize(std::string_view& s);
    /** Serialize the signature to a string*/
    std::string Serialize() const;
};

enum class ESymbolIndent
{
    INVALID,
    LEFT,
    RIGHT,
    CENTER,
    MAX_HORIZONTAL=CENTER,
    TOP,
    BOTTOM,
    ABOVE_CENTERLINE,
    CENTERLINE,
    BELOW_CENTERLINE,
};

/** Represents the `symbol` command.*/
class Symbol : public LabelledArc
{
public:
    enum ESymbolPosType
    {
        INVALID = 0,
        ABOVE,
        BELOW,
        RIGHTOF,
        LEFTOF,
        AROUND
    };
protected:
    /** Describes what type of a symbol it is */
    enum ESymbolType {
        ARC,       ///<An arc symbol
        RECTANGLE, ///<A rectangle symbol
        ELLIPSIS,  ///<An ellipsis (three vertical dots)
        CROSS,     ///<A big X
        SHAPE,     ///<A shape
        INLINE,    ///<An inlined chart
    };
    static constexpr double ellipsis_space_ratio=2.0/3.0; ///<The ratio of the height of the dots and the space between them for ellipses
    static constexpr double def_arc_size=10;      ///<Default x and y coordinates for arcs of unspecified size
    static constexpr double def_rectangle_size=10;///<Default x and y coordinates for rectangles of unspecified size
    static constexpr double def_shape_size=50;    ///<Default x and y coordinates for shapes of unspecified size
    ESymbolType             symbol_type;          ///<What type of symbol this is
    //These are used only for inlined charts
    std::string_view        inline_lang;          ///<The language name if symbol_type==INLINE
    FileLineColRange        symbol_type_loc;      ///<The location of the language name if symbol_type==INLINE
    InliningData            inlining_data;        ///<If the symbol contains an inlined chart, this holds it.
    InlineSignature         signature;            ///<To be used in InlineSignatureCatalog. Filled in only in AddInline.
    //These fields are used if Symbol is positioned by reference to another element
    ESymbolPosType          pos_type = INVALID;   ///<INVALID if we have the classic left/right/center and marker-based positions, else the kind of position (above/below/leftof/rightof/around)
    std::string_view        pos_refname;          ///<The refname of the referenced element we want to be part of. Empty if none (prev element)
    FileLineCol             pos_refname_loc;      ///<The location of the refname above
    ArcBase*                pos_reftarget = nullptr;///<The element we are in relation to (either looked up from pos_refname or use the previous element).
    std::vector<std::string>pos_entityname;       ///<If pos_reftarget is an EntityCommand, this holds the name of the entities we aim for.
    OptAttrLine<ESymbolIndent>pos_indent;         ///<To which end of the referenced element we align to.
    double                  pos_distance = 0;     ///<The distance to keep from the element
    double                  pos_offset = 0;       ///<The horizontal (ABOVE, BELOW) or vertical (LEFTOF RIGHTOF) offset 
    Block                   pos_adjust{0,0,0,0};  ///<For AROUND you can adjust the 4 sides by +- pixels.
    FileLineCol             pos_adjust_loc;       ///<The location of the adjustment above
    //These fields are used if positioned via markers and entity lines (pos_type==INVALID)
    ExtVertXPos             hpos1;                ///<First horizontal position
    ExtVertXPos             hpos2;                ///<Second (optional) horizontal position
    NamePair                vpos;                 ///<The two markers for vertical position
    double                  gap1;                 ///<The gap to apply with 'at entity+' and 'at entity-' clauses
    double                  gap2;                 ///<The increase of gap to apply with 'at entity++' and 'at entity--' clauses compared to +/- only. (So the total gap to apply for ++/-- is gap1+gap2.)
    //Attributes
    OptAttrLine<double>     xsize;                ///<(optional) X size specified by the user. (used for 'arc' and 'rectangle')  <0 values mean 'scale with reftarget' for ByRef Symbols.
    OptAttrLine<double>     ysize;                ///<(optional) Y size specified by the user. (used for 'arc' and 'rectangle'). <0 values mean 'scale with reftarget' for ByRef Symbols.
    OptAttrLine<double>     size;                 ///<Size of the dots in the ellipsis, that of the shape, etc. A multiplier, with SMALL defaulting to 0.5
    //Dynamically calculated
    mutable double          act_size1;            ///<Store the activation size of hpos1.entity1 in PostParseProcess()
    mutable double          act_size2;            ///<Store the activation size of hpos2.entity1 (if exists) in PostParseProcess()
    mutable Block           outer_edge;           ///<The computed bounding box of the symbol
    bool SetSymbolType(std::string_view symbol, const FileLineColRange& symbol_loc, bool text_at);
    static ExtVertXPos::ERelativeTo CalcRelToForTextCommand(const VertXPos *vpos);
    Range CalculateOuterEdgeXFromXSizeOrHPos() const;
    void CalculateAreaFromOuterEdge(Canvas &canvas);
public:
    bool                    layout_warning = true;///<If set, we emit layout warnings if the symbol is attached to a ParallelBlock with one_by_one_merge and spans _above_ the ParallelBlock 
    Symbol(MscChart*, std::string_view symbol, const FileLineColRange& symbol_loc, const FileLineColRange& l, const NamePair *enp,
           const ExtVertXPos *vxpos1, const ExtVertXPos *vxpos2);
    Symbol(MscChart*msc, std::string_view symbol, const FileLineColRange& symbol_loc, const FileLineColRange& l, const VertXPos *vpos1, const FileLineColRange &at_pos);
    Symbol(MscChart* msc, std::string_view symbol, const FileLineColRange& symbol_loc, const FileLineColRange& l,
           std::string_view position, const FileLineColRange& pos_loc, std::string_view refname = {}, const FileLineColRange& ref_loc = {});
    bool CanBeNoted() const override { return true; }
    bool CanBeAlignedTo() const override { return !IsByRef(); }
    bool IsByRef() const override { return pos_type != INVALID; }
    ArcBase* GetRefTarget() const override { return pos_reftarget; }
    void SetStyleBeforeAttributes(AttributeList *) override {} //styles are set in constructor
    bool AddAttribute(const Attribute &) override;
    static void AttributeNames(Csh &csh, int kind);
    static bool AttributeValues(std::string_view attr, Csh &csh, int kind);
    void AddInline(std::string_view inline_text, const FileLineColRange &pos);
    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 SetYSize();
    void Layout(Canvas &canvas, AreaList *cover) override;
    bool LayoutByRef(Canvas& canvas, const AreaList &target_cover, AreaList* cover) override;
    void ShiftBy(double y) override;
    /** We are to be ignored if we are off-line (outer_edge is valid) or we cannot rearrange if in-line */
    double SplitByPageBreak(Canvas &/*canvas*/, double /*netPrevPageSize*/,
                            double /*pageBreak*/, bool &/*addCommandNewpage*/,
                            bool /*addHeading*/, ArcList &/*res*/) override
                            {return outer_edge.y.IsInvalid() ? -2 : -1;}
    void PlaceWithMarkers(Canvas &cover) override;
    void PostPosProcess(Canvas &, Chart *ch) override;
    void RegisterLabels() override;
    void CollectIsMapElements(Canvas &canvas) override;
    void Draw(Canvas &canvas, EMscDrawPass pass) const override;
    const InliningData* GetInlinedChartData() const override { return inlining_data.chart ? &inlining_data : nullptr; }
    void InlinedChartGUIStateChange() const override;
};

struct score_t;

/** Represents notes and comments */
class Note : public LabelledArc
{
public:
    const bool       is_float;         ///<True for a note, false for a comment
protected:
    MscElement      *target;           ///<The arc the note or comment is made to or on
    string           point_toward;     ///<The name of entity or marker specified after an `at` clause for notes
    FileLineColRange point_toward_pos; ///<The location of name of the entity or marker in the input file
    OptAttr<int>     float_dist;       ///<User preferences on note placement: how far it shall be from the target (-1:0:+1)
    int              float_dir_x;      ///<User preferences on note placement: left or right (-1, 0, +1)
    int              float_dir_y;      ///<User preferences on note placement: up or down (-1, 0, +1)

    mutable XY       halfsize;         ///<Half of the calculated width and height of a note
    mutable XY       pos_center;       ///<Calculated center of the note
    mutable XY       point_to;         ///<The tip of the arrow for a note

public:
    Note(MscChart*, std::string_view pt = {}, const FileLineColRange& ptm = FileLineColRange());
    Note(MscChart*msc, ESide side);
    ~Note() override;
    bool CanBeNoted() const override { return false; }
    bool CanBeAlignedTo() const override { return false; }
    const std::string &GetPointTowardText() const { return point_toward; }
    /** Return the target arc of the note or command */
    MscElement *GetTarget() const {return target;}
    /** Set the target to an appearance of an entity. */
    void SetTarget(EntityApp *e) {target = e;}
    /** Set the target to an Arrow or BoxSeries - useful for JoinSeries. */
    void SetTarget(ArcBase* e) { target = e; }
    /** Invalidate this note or comment. Called by MscChart::InvalidateNotesToThisTarget() if its target becomes hidden*/
    void Invalidate() {valid = false;}
    void SetStyleBeforeAttributes(AttributeList *l) override; //no refinement: style set in constructor
    bool AddAttribute(const Attribute &) override;
    static void AttributeNames(Csh &csh, bool is_float);
    static bool AttributeValues(std::string_view attr, Csh &csh, bool is_float);
    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;
    void ShiftBy(double y) override;
    /** A special shift function - we do not, in general shift, but comments shift with their target (called from MscElement::ShiftBy())*/
    virtual void ShiftCommentBy(double y);
    double SplitByPageBreak(Canvas &/*canvas*/, double /*netPrevPageSize*/,
                            double /*pageBreak*/, bool &/*addCommandNewpage*/,
                            bool /*addHeading*/, ArcList &/*res*/) override {return -2;} //Ignore comments for pagination (moved with their owner)

    /** @name Note placement calculation
     * Note placement algorithm works as follows.
     * We split the canvas around the target arc into 3*8 regions coming from
     * 3 distances and 8 directions. The regions of the same distance are called
     * region belts and are 2D expanded versions of the target cover. A specific
     * region is a slice of the region belt.
     * We try to place the note at the best place in each region and then
     * score each region and pick the one with the highest score.
     * - a weak default differentiation, so we get upper right notes if no user preference and
     *   its all the some otherwise (no obstacles anywhere)
     * - regions preferred by the user via attributes are scored much higher
     * - if the note in the region overlaps with a critical part of another arc
     *   (such as a label or an arrowhead), we downscore it very badly. Overlaps
     *   to less critical areas are also penalized, but not so heavily
     * - if the pointer arrives to the target arc at a funny angle, we also penalize.
     *
     * @{ */
protected:
    /** Calculate the width of a pointer for a note*/
    double pointer_width(double distance) const;
    /** Returns the contour of the pointer of a note*/
    Contour cover_pointer(const XY &point_to, const XY &center) const;
    /** The outline of the note calculated from `halfsize`*/
    Contour CoverBody(const XY &center) const;
    /** The outline of the pointer calculated from `halfsize`. Disjoint from the body.*/
    Contour CoverPointer(const XY &pointto, const XY &center) const
        {return cover_pointer(pointto, center) - CoverBody(center);}
    /** The combined cover of the body and the pointer calculated from `halfsize`.*/
    Contour CoverAll(const XY &pointto, const XY &center) const
        {return cover_pointer(pointto, center) + CoverBody(center);}
    static Contour GetRegionMask(const Block &outer, const XY &center, int dir_x, int dir_y);
    std::optional<std::array<std::pair<XY, XY>,2>> GetPointerTarget() const;
    void CoverPenalty(const XY &pointto, const XY &center,
                      const Contour &block_all, const Contour &block_imp,
                      score_t &cover_penalty) const;
    void SlantPenalty(const XY &pointto, const XY &center, const XY &tangent,
                      score_t &slant_penalty) const;
    static bool GetAPointInside(const Contour &c, const XY &p1, const XY &p2, XY&ret);
    static bool GetAPointInside(const DoubleMap<bool> &map, double &ret);
public:
    void PlaceFloating(Canvas &canvas);
    void PlaceSideTo(Canvas &canvas, AreaList *cover, double &y);
    //@}

    void RegisterLabels() override;
    void CollectIsMapElements(Canvas &canvas) override;
    void Draw(Canvas &canvas, EMscDrawPass pass) const override;
};

/** The horizontal line separating end notes from the chart. */
class EndNoteSeparator : public Command
{
public:
    explicit EndNoteSeparator(MscChart *m) :Command(EArcCategory::TINY_EFFORT, m) {}
    void Layout(Canvas &canvas, AreaList *cover) override;
    virtual void PostPosProcess(Canvas &, Chart *ch) override;
    void Draw(Canvas &canvas, EMscDrawPass pass) const override;
};

} //namespace

#endif //COMMANDS_H