/*
    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 cgen_arrowhead.h The declaration of arrowhead classes to measure and draw arrowheads.
 * @ingroup libcgencommon_files */

#if !defined(ARROWHEAD_H)
#define ARROWHEAD_H
#include <vector>
#include "cgen_attribute.h"
#include "contour.h"


class Canvas;

enum class ESingleArrowType
{
    NONE,         ///<No arrowhead
    NORMAL,       ///<Line: a triangle, Block: wider than the body
    DIAMOND,      ///<Line: A diamond shape, Block: diamond on targetline wider than body
    NSDIAMOND,    ///<Line: A diamond shape, that is not symmetric, Block: diamond beyond targetline, not wider than body; drawn circle when midtype
    DOT,          ///<Line: A circle, Block: circle on targetline wider than body
    NSDOT,        ///<Line: A circle, that is not symmetric, Block: circle beyond targetline, not wider than body; drawn circle when midtype
    BOX,          ///<Line: A square on the target line, Block: A square on the target line, wider than body
    NSBOX,        ///<Line: A square that is not symmetric, Block: A square beyond targetline, not wider than body; drawn square when midtype
    SHARP,        ///<Line: A triangle with miters, Block: a sharper triangle
    INV,          ///<Line: The inverted triangle, Block: A wedge (inverted triangle)
    VEE,          ///<Line:Like a '->' sign (from graphviz), Block a diamond before the target line, not wider than the body
    TEE,          ///<Line: A block just before the target line, Block: A block wider than body on the target line
    //Line arrows only
    LINE,         ///<just two lines
    CROW,         ///<Like a '-<' sign (from graphviz)
    CURVE,        ///<Like a '-)' sign (from graphviz)
    ICURVE,       ///<Like a '-(' sign (from graphviz)
    TICK,         ///<A bit thick line just on the target line (symmetric). Good for when there is no target line...
    JUMPOVER,     ///<jump over line: used for skip
    //Block arrows only
    STRIPES,      ///<Block only: Three rectangles of increasing size
    TRIANGLE_STRIPES ///<Block only: Three triangles of increasing size
};

enum class ESingleArrowSide
{
    BOTH, ///<The arrowhead shows on both sides of the arrow line
    LEFT, ///<The arrowhead shows on the left side of the line (as seen towards the target of the arrow)
    RIGHT ///<The arrowhead shows on the right side of the line (as seen towards the target of the arrow)
};

constexpr ESingleArrowSide Flip(ESingleArrowSide s, bool flip = true) { return s==ESingleArrowSide::BOTH || !flip ? s : ESingleArrowSide(3-unsigned(s)); }

/** True if the type is applicable for line arrows */
constexpr bool GoodForArrows(ESingleArrowType t) { return t<=ESingleArrowType::JUMPOVER; }
/** True if the arrowhead is of shape of an actual triangle (and good for arrows). Used to determine spacing for 'line' type of triangles. E.g., >> can be laid out closer. */
constexpr bool IsTriangleShape(ESingleArrowType t) { return ESingleArrowType::NORMAL==t || t==ESingleArrowType::LINE; }
/** True if the arrowhead is of line type. Used to determine arrow clipping. */
constexpr bool IsLine(ESingleArrowType t) { return ESingleArrowType::CURVE==t || t==ESingleArrowType::LINE || t==ESingleArrowType::ICURVE; }
/** True if the shape has a different 'empty' version.*/
constexpr bool CanBeEmpty(ESingleArrowType t) { return ESingleArrowType::NONE!=t && ESingleArrowType::LINE!=t && ESingleArrowType::CURVE!=t && ESingleArrowType::ICURVE!=t && ESingleArrowType::JUMPOVER!=t; }
/** True if the shape has a different 'left'/'right' version.*/
constexpr bool CanBeHalf(ESingleArrowType t) { return ESingleArrowType::NONE!=t && ESingleArrowType::JUMPOVER!=t; }
/** True if the arrowhead does not cover the arrow line at the tip. */
constexpr bool DoesNotCoverArrowLineAtTip(ESingleArrowType t) { return t==ESingleArrowType::CROW || t==ESingleArrowType::ICURVE || t==ESingleArrowType::JUMPOVER; }
/** True if the arrowhead has special clipping rules in a row. */
constexpr bool SpecialLineClip(ESingleArrowType t) { return IsLine(t) && !DoesNotCoverArrowLineAtTip(t); }
/** True if the type symmetric to a horizontal line, but is not 'none'. */
constexpr bool IsSymmetricAndNotNone(ESingleArrowType t) { return ESingleArrowType::DIAMOND==t || t==ESingleArrowType::DOT || t==ESingleArrowType::BOX ||  t==ESingleArrowType::TICK || t==ESingleArrowType::JUMPOVER; }
/** True if the type symmetric to a horizontal line. */
constexpr bool IsSymmetricOrNone(ESingleArrowType t) { return t==ESingleArrowType::NONE || IsSymmetricAndNotNone(t); }
/** True if the type is applicable for block arrows */
constexpr bool GoodForBlockArrows(ESingleArrowType t) { return t<ESingleArrowType::LINE || t>=ESingleArrowType::STRIPES; }
/** True if the arrowhead breaks a block arrow into two. */
constexpr bool BreaksBlockArrow(ESingleArrowType t) { return t==ESingleArrowType::NORMAL|| t==ESingleArrowType::VEE || t==ESingleArrowType::TEE || t==ESingleArrowType::SHARP || t==ESingleArrowType::INV || t==ESingleArrowType::STRIPES || t==ESingleArrowType::TRIANGLE_STRIPES; }
/** Returns if this arrowhead type 't' is applicable for this kind of arrow class 'at'.*/
constexpr bool GoodForThisArrowType(EArcArrowType at, ESingleArrowType t) { return at==EArcArrowType::ARROW ? GoodForArrows(t) : at==EArcArrowType::BIGARROW ? GoodForBlockArrows(t) : at==EArcArrowType::ANY; }

constexpr static ESingleArrowType _convert_earrow_to_esinglearrow[unsigned(EArrowType::TRIANGLE_STRIPES)+1] = {
    ESingleArrowType::NONE, ESingleArrowType::NONE, ESingleArrowType::NORMAL, ESingleArrowType::NORMAL, /*INVALID, NONE, SOLID, EMPTY */
    ESingleArrowType::LINE, ESingleArrowType::LINE, ESingleArrowType::DIAMOND, ESingleArrowType::DIAMOND, /*LINE, HALF, DIAMOND, DIAMOND_EMPTY*/
    ESingleArrowType::DOT, ESingleArrowType::DOT, ESingleArrowType::SHARP, ESingleArrowType::SHARP, /*DOT, DOT_EMPTY, SHARP, SHARP_EMPTY*/
    ESingleArrowType::VEE, ESingleArrowType::VEE, ESingleArrowType::NSDIAMOND, ESingleArrowType::NSDIAMOND, /*VEE, VEE_EMPTY, NSDIAMOND, NSDIAMOND_EMPTY*/
    ESingleArrowType::NSDOT, ESingleArrowType::NSDOT, ESingleArrowType::JUMPOVER, /*NSDOT, NSDOT_EMPTY, JUMPOVER*/
    ESingleArrowType::NORMAL, ESingleArrowType::NORMAL, ESingleArrowType::LINE, ESingleArrowType::LINE, /*DOUBLE, DOUBLE_EMPTY, DOUBLE_LINE, DOUBLE_HALF*/
    ESingleArrowType::NORMAL, ESingleArrowType::NORMAL, ESingleArrowType::LINE, ESingleArrowType::LINE, /*TRIPLE, TRIPLE_EMPTY, TRIPLE_LINE, TRIPLE_HALF*/
    ESingleArrowType::INV, ESingleArrowType::STRIPES, ESingleArrowType::TRIANGLE_STRIPES /*EMPTY_INV, STRIPES, TRIANGLE_STRIPES*/
};

constexpr static ESingleArrowType _convert_earrow_to_block_esinglearrow[unsigned(EArrowType::TRIANGLE_STRIPES)+1] = {
    ESingleArrowType::NONE, ESingleArrowType::NONE, ESingleArrowType::NORMAL, ESingleArrowType::VEE, /*INVALID, NONE, SOLID, EMPTY */
    ESingleArrowType::NORMAL, ESingleArrowType::INV, ESingleArrowType::DIAMOND, ESingleArrowType::NSDIAMOND, /*LINE, HALF, DIAMOND, DIAMOND_EMPTY*/
    ESingleArrowType::DOT, ESingleArrowType::NSDOT, ESingleArrowType::SHARP, ESingleArrowType::SHARP, /*DOT, DOT_EMPTY, SHARP, SHARP_EMPTY*/
    ESingleArrowType::NONE, ESingleArrowType::NONE, ESingleArrowType::NSDIAMOND, ESingleArrowType::NSDIAMOND, /*VEE, VEE_EMPTY, NSDIAMOND, NSDIAMOND_EMPTY*/
    ESingleArrowType::NONE, ESingleArrowType::NONE, ESingleArrowType::NONE, /*NSDOT, NSDOT_EMPTY, JUMPOVER*/
    ESingleArrowType::NORMAL, ESingleArrowType::NORMAL, ESingleArrowType::NONE, ESingleArrowType::NONE, /*DOUBLE, DOUBLE_EMPTY, DOUBLE_LINE, DOUBLE_HALF*/
    ESingleArrowType::NORMAL, ESingleArrowType::NORMAL, ESingleArrowType::NONE, ESingleArrowType::NONE, /*TRIPLE, TRIPLE_EMPTY, TRIPLE_LINE, TRIPLE_HALF*/
    ESingleArrowType::INV, ESingleArrowType::STRIPES, ESingleArrowType::TRIANGLE_STRIPES /*EMPTY_INV, STRIPES, TRIANGLE_STRIPES*/
};

/** Convert from EArrowType to ESingleArrowType.
 * In case of double or triple variants, we return their single version.
 * 'block' tells us if the conversion is for a block arrow or a line arrow. */
constexpr ESingleArrowType ConvertArrowType(EArrowType t, bool block) noexcept { return unsigned(t)<=unsigned(EArrowType::TRIANGLE_STRIPES) ? (block ? _convert_earrow_to_block_esinglearrow : _convert_earrow_to_esinglearrow)[unsigned(t)] : ESingleArrowType::NONE;}
/* Returns the side of an EArrowType */
constexpr ESingleArrowSide ConvertArrowSide(EArrowType t) noexcept { return IsHalf(t) ? ESingleArrowSide::LEFT : ESingleArrowSide::BOTH; }

/** Describes an actual arrowhead element (a 'single' arrowhead) that can be drawn.
 * Contains functions to return arrowhead dimensions, coverage and actual drawing.
 * Both for line arrowheads and block arrowheads.*/
class SingleArrowHead
{
    friend class ArrowHead;
    /** Lists options of what part of an arrow line is covered. */
    enum EAfterMe
    {
        ALL_COVERED = 0,   ///<The full width of the arrow line is covered.
        LEFT_COVERED = 1,  ///<The left side of the arrow line is covered, when looking towards its end.
        RIGHT_COVERED = 2, ///<The right side of the arrow line is covered, when looking towards its end.
        NONE_COVERED = 3   ///<None of the arrow line is covered
    };
    EAfterMe line_needed_after_me(bool flip, const SingleArrowHead * front, const SingleArrowHead * back) const;
    bool are_we_part_of_a_half_line_series(const SingleArrowHead * front, const SingleArrowHead * back) const;
    enum EClipLineType
    {
        CLIP_NO = 0,            ///<In the clip do not include any part of the arrow line.
        CLIP_LEFT = 1,          ///<In the clip include the left side of the arrow line, when looking towards its end.
        CLIP_RIGHT = 2,         ///<In the clip include the right side of the arrow line, when looking towards its end.
        CLIP_ALL = 3,           ///<Include the full width of the arrow line in the clip.
        CLIP_ONLY_FRONT = 4,    ///<Do not include the full length of the arrowhead, only the half of it ("WHRetval::offset" from the tip)
        CLIP_ADD_COVER = 8,     ///<Clip the whole cover of the arrowhead from the arrow line (in addition to the above)
        CLIP_MINUS_COVER = 16   ///<Keep the whole cover of the arrowhead (substract it from the above)

    };
    EClipLineType clipline_type(bool flip, bool ignore_opaque, const SingleArrowHead * front, const SingleArrowHead * back) const;
    Contour curvy_line_cover(const Path &path, const PathPos &pos, bool forward, bool flip,
                             unsigned num, double *gap1, double *gap2, double *length) const;
public:
    /** @name Horizontal width & vertical depth of the arrow heads at EArrowType::NORMAL.
     * @{ */
    static constexpr double baseArrowWidth = 17;   ///<X-size of triangle for 100% size
    static constexpr double baseArrowHeight = 10;  ///<half of the Y-size of triangle for 100% size
    static constexpr double baseDiamondSize = 10;  ///<X and Y size for a diamond for 100% size
    static constexpr double baseDotSize = 9;       ///<circle radius for dots for 100% size
    static constexpr double baseBoxSize = 6;       ///<box width for boxes in 100% size
    static constexpr double baseTeeWidth = 2;      ///<base tee width
    static constexpr double baseBlockSize = 25;    ///<Block arrow size for 100% size
    static constexpr double SHARP_MUL_1 = 1.2;     ///<How much wider is a sharp arrow than a normal one
    static constexpr double SHARP_MUL_2 = 0.7;     ///<What percentage of the width of a sharp arrow is at the line
    static constexpr double VEE_MUL_1 = 1;         ///<How much wider is vee arrow than a normal one
    static constexpr double VEE_MUL_2 = 0.5;       ///<What percentage of the width of a vee arrow is at the line
    /** @} */

    ESingleArrowType type;  ///<The type of the arrowhead.
    ESingleArrowSide side;  ///<Whether we are cropped to one side only
    bool             empty; ///<If true we are hollow inside. Ignored for block arrows.
    XY               scale; ///<Compared to 'normal' size
    LineAttr         line;  ///<The color and line type of the arrowhead. Not used for block arrows.

    SingleArrowHead() noexcept = default;
    SingleArrowHead(ESingleArrowType t, ESingleArrowSide s, bool e, const XY &sc, const LineAttr &l) noexcept : type(t), side(s), empty(e), scale(sc), line(l) {}
    SingleArrowHead(EArrowType t, bool block, const XY &s, const LineAttr &l = LineAttr()) noexcept : type(ConvertArrowType(t, block)), side(ConvertArrowSide(t)), empty(!block && IsEmpty(t)), scale(s), line(l) {}

    /** @name Functions and structures for line arrowheads
     * We provide two sets of functions. One computes/draws the single arrowhead on a straight line
     * (assuming left-to-right direction along the x-axis, so you will have to rotate if another
     * direction is needed); and the other computes/draws the arrowhead on an arbitrary path that
     * may be curvy or may bend. Note that the latter is a lot more resource intensive and works
     * only if the radius of curves and the angle of bends is large enough for the arrowhead.
     * @{ */
    /** Structure to return the dimensions of a single line arowhead (an arrowhead segment).
     *  Here are two examples: SOLID and DIAMOND:
     * @verbatim
     *         |   ___                          |     ___
     *      \  |    |half                      /:\     | half
     *       \ |    |height                   / : \    | height
     *  ------>|   ---               --------<  :  >  ---
     *       / |                              \ : /
     *      /  |   after_x_extent=0            \:/
     *         |                                |
     *     |<->|                            |<->|<->|
     *   before_x_extent               before_*   after_x_extent
     * @endverbatim
     */
    struct WHRetval
    {
        double half_height;     ///<The half of the height of the arrowhead. (y dimension if arrow is horizontal)
        double before_x_extent; ///<How wide the arrow (x dimension if arrow is horizontal) is before the target line
        double after_x_extent;  ///<How wide the arrow (x dimension if arrow is horizontal) is after the target line. 0 for non-symmetric arrowheads
    };
    /** Structure to return detailed dimensions of a single line arrowhead.
     * Offset tells where the tip of next arrowhead element shall be compared to our tip.
     * Usually it equals to before_x_extent, but not for LINE and HALF_LINE arrowheads, where it is smaller
     * if the next element is also a line to typeset DOUBLE_LINE closer.*/
    struct WHORetval : public WHRetval
    {
        double offset; ///<How much to offset along the arrow line to the next arrowhead. Small for LINE and HALF.
    };
    WHORetval WidthHeightOffset(const LineAttr &mainline_before, const LineAttr &mainline_after,
                                const SingleArrowHead *front=nullptr,  const SingleArrowHead *back=nullptr) const;
    Contour Cover(const XY &xy, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after,
                  const SingleArrowHead *front=nullptr,  const SingleArrowHead *back=nullptr) const;
    Contour ClipLine(const XY &xy, bool flip, bool ignore_opaque, const LineAttr &mainline_before, const LineAttr &mainline_after,
                     const SingleArrowHead *front=nullptr,  const SingleArrowHead *back=nullptr) const;
    void Draw(Canvas &canvas, const XY &xy, bool flip,
              const LineAttr &mainline_before, const LineAttr &mainline_after,
              const SingleArrowHead *front=nullptr, const SingleArrowHead *back=nullptr) const;

    Contour Cover(const Path &path, const PathPos &pos, bool forward, bool flip,
                  const LineAttr &mainline_before, const LineAttr &mainline_after,
                  const SingleArrowHead *front=nullptr,  const SingleArrowHead *back=nullptr) const;
    Contour ClipLine(const Path &path, const PathPos &pos, bool forward, bool flip, bool ignore_opaque,
                     const LineAttr &mainline_before, const LineAttr &mainline_after,
                     const SingleArrowHead *front=nullptr,  const SingleArrowHead *back=nullptr,
                     const Contour *cached_cover=nullptr) const;
    void Draw(Canvas &canvas, const Path &path, const PathPos &pos, bool forward, bool flip,
              const LineAttr &mainline_before, const LineAttr &mainline_after,
              const SingleArrowHead *front=nullptr,  const SingleArrowHead *back=nullptr,
              const Contour *cached_cover = nullptr) const;
    /** @} */


    /** @name Functions for block arrowheads
     * @{ */

     /** Structure to return detailed dimensions of a block arrowhead.
      * Here are a few examples:
      * @verbatim
      *
      *                   | ___
      *            \\     |  | y_extent
      *             \ \   |  | above_body
      * body--- ----+   \ | ---
      * hei- |           >|
      * ght --- ----+   / |
      *             / /   |
      *            //     |
      *                   |
      *             |<--->| body_connext_before: where shall we start adding the body rectangle
      *            |<---->| before_x_extent: the general size of the arrowhead
      *           |<----->| before_margin: how much space is needed if we dont want to overlap the arrowhead
      *                   |       (usually x_extent + linewidth)
      *
      * Add activation is used when the target is thick
      *
      *                    |<----This is the middle of the target line
      *                    |
      *                  |||||<----This is how wide the target line is
      *             |\   |||||
      *     --------+  \ |||||
      *                 >|||||
      *     --------+  / |||||
      *             |/   |||||
      *                  |||<-------This is where the tip ends up if add_activation is 0.
      *                  |||||<-----This is where the tip ends up if add_activation is +1
      *                  |<---------This is where the tip ends up if add_activation is -1
      *
      *                  |||||
      *                  ||A||            For a symmetric arrowhead add_activation is usually 0.
      *                  /   \
      *           ------       ------
      *           ------       -------
      *                  \   /
      *                  ||V||
      *                  |||||
      *
      *                  |||||           For these DIAMOND_EMPTY and DOT_EMPTY-like arrowheads
      *            ----------\           add_activation is actually +1 if they occur at the end
      *                        \         and not in the middle of an arrow.
      *                         >
      *                        /
      *            -----------/
      *                  |||||
      * @endverbatim
      */
    struct BlockWHRetval
    {
        double y_extent_above_body; ///<How much the arrowhead expands above and below the body of the arrow.
        double before_x_extent;     ///<How wide the arrowhead (x dimension if arrow is horizontal) is before the target line
        double after_x_extent;      ///<How wide the arrowhead (x dimension if arrow is horizontal) is after the target line. 0 for non-symmetric arrowheads
        double before_margin;       ///<How much space do we need before the arrowhead
        double after_margin;        ///<How much space do we need after the arrowhead
        int add_activation;         ///<If -1, activation moves the tip towards the source (before), if +1 then towards the arrow pointing, if 0, then it has no impact.
        double body_connect_before; ///<Offset from tip where we shall start adding the body.
        double body_connect_after;  ///<Offset from tip where we shall start adding the body. 0 for non-symmetric arrowheads.
    };
    /**Change type, side and empty to be good for block arrows.*/
    void EnsureBlock() {
        if (type==ESingleArrowType::LINE) type = ESingleArrowType::NORMAL;
        else if (!GoodForBlockArrows(type)) type = ESingleArrowType::NONE;
        empty = false;
    }
    BlockWHRetval BlockWidthHeight(double body_height, const LineAttr & line, bool middle) const;
    double BlockTextMargin(Contour text_cover, bool left_margin, double sy, double dy, const LineAttr & line) const;
    Contour BlockContour(double x, double sy, double dy, const LineAttr & line, bool middle) const;
    Path BlockMidPath(double x, double sy, double dy, const LineAttr & line) const;
    /** @} */

};

/** A structure that holds the computed cover and position of
 * a single arrowhead instance on a specific path.
 * Supplying this will make it easier to calculate clip for line and target & drawing. */
struct SingleArrowHeadCoverCache
{
    PathPos tip_pos; ///<The position of the tip on the path
    Contour cover;   ///<The cover of the arrowhead - non-drawing edges are invisible
};

/** A vector holding the computed cover info of a given (multi-segment) arrowhead.
 * The first element is embedded as 99% of arrowheads are single-element.
 * Supplying this will make it easier to calculate clip for line and target & drawing. */
struct ArrowHeadCoverCache {
    SingleArrowHeadCoverCache first;  ///<The cache for the first segment
    std::vector<SingleArrowHeadCoverCache> further; ///<The caches for further segments
};

/** An arrowhead at one end (or in the middle) of an arrow.
 * It can be a series of arrowhead elements, zero, one or more.
 * Supports arrows via arbitrary path, including block arrows.
 * We aim to ensure that no elements of the series is NONE or INVALID.
 *
 * Here is alternative No. 1 how arrowheads are meant to work (this method is
 * suitable when you primarily draw arrow to straight lines):
 * - You should create an Attribute class, where you allow your language to specify
 *   arrowheads. There you can use OptAttr<> elements. You can in the end
 *   allow or disallow arrowhead series, inlcude/exclude block arrows, allow
 *   scaling for arrowheads (or just allow built in sizes), allow or disallow
 *   individual sizing or coloring of arrowheads in a series.
 *   Or, you can just take TwoArrowAttr or FullArrowAttr found in cgen_attributes.h.
 * - Then you can create a child class that holds a number of mutable ArrowHead members, for
 *   various head/tail/mid, etc arrowheads of your arrows. Examples of that are
 *   TwoArrowHeads and FullArrowHeads in this file.
 *   You can specify dimensioning and drawing interfaces for your class.
 *   Make your class the descendant for the above Attribute class.
 * - Add your attribute class to each chart element where you support arrowheads.
 *   This can be made via SimpleStyleWithArrow<> (found in styles.h) by specifying
 *   your arrow Attribute class as template parameter. Then your language will have
 *   styles where it is possible to specify arrowheads via the attributes you have
 *   defined in the Attribute class described above.
 * - When you have finished parsing and all attributes of your elements are gathered,
 *   you can create the actual ArrowHeads objects from the attributes of the elements.
 *   Use CreateArrowHeads() function as an example for this.
 *
 * Alternative No. 2 is better suited to when you draw arrowheads to arbitrary paths.
 * In this case it is important to cache the computed cover for each arrowhead instance,
 * since it speeds up drawing and line clip calculations a lot. So then you should:
 * - Still create Attribute and ArrowHead classes (usually OneArrowAttr/TwoArrowAttr and
 *   OneArrowHead/TwoArrowHeads is sufficient). Then you shall create an instance class
 *   associeated that has ArrowHeadCoverCache objects for all arrowheads of the line
 *   e.g., start and end. OneArrowHeadInstance/TwoArrowHeadsInstance are examples.
 * - Then, when you have finished parsing create the instance objects for all actual
 *   arrowheads that you want to draw. These will then store the cover of each arrowhead
 *   making clip line calculation, drawing and re-drawing fast. */
class ArrowHead
{
protected:
    SingleArrowHead single;                  ///<The arrowhead segment, if we have only one.
    std::vector<SingleArrowHead> series;     ///<The list of arrowhead segments, if we have more than one.
    void Convert(EArrowType t, bool block, const XY &sc, const LineAttr &l=LineAttr());
    void PrepToAdd() { if (series.size()==0 && single.type!=ESingleArrowType::NONE) series.push_back(single); }
    void TestIfSingle() { if (series.size()==1) {single = series.front(); series.clear(); series.reserve(0);} }
    const SingleArrowHead *fsah() const { return series.size() ? &series.front() : nullptr; }
    const SingleArrowHead *lsah() const { return series.size() ? &series.back() : nullptr; }
public:
    static const ArrowHead noneArrowHead; ///<A pre-established 'NONE' arrowhead, that can be returned, when needed.
    using WHRetval = SingleArrowHead::WHRetval;
    explicit ArrowHead(ESingleArrowType t = ESingleArrowType::NONE, ESingleArrowSide s = ESingleArrowSide::BOTH, bool o = false, const XY &sc = XY(ArrowScale(EArrowSize::SMALL), ArrowScale(EArrowSize::SMALL)), const LineAttr &l = LineAttr()) :
        single(t, s, o, sc, l) {}
    ArrowHead(ESingleArrowType t, ESingleArrowSide s, bool o,  double sc, const LineAttr &l = LineAttr()) :
        single(t, s, o, XY(sc, sc), l) {}
    ArrowHead(ESingleArrowType t, ESingleArrowSide s, bool o, EArrowSize sc, const LineAttr &l = LineAttr()) :
        single(t, s, o, XY(ArrowScale(sc), ArrowScale(sc)), l) {}
    /** Initializes using a single arrowhead type (which can still be DOUBLE, for example) and size. Uses default LineAttr.*/
    ArrowHead(EArrowType t, bool block, EArrowSize s) { Convert(t, block, XY(ArrowScale(s), ArrowScale(s))); }
    /** Initializes using a single arrowhead type (which can still be DOUBLE, for example) and scale relative to NORMAL size. Uses default LineAttr.*/
    ArrowHead(EArrowType t, bool block, double scale) { Convert(t, block, XY(scale, scale)); }
    /** Initializes using a single arrowhead type (which can still be DOUBLE, for example) and scale relative to NORMAL size. Uses default LineAttr.*/
    ArrowHead(EArrowType t, bool block, const XY &scale) { Convert(t, block, scale); }
    /** Initializes using a single arrowhead type (which can still be DOUBLE, for example), scale relative to NORMAL size and line attribute.*/
    ArrowHead(EArrowType t, bool block, EArrowSize s, const LineAttr &l) { Convert(t, block, XY(ArrowScale(s), ArrowScale(s)), l); }
    /** Initializes using a single arrowhead type (which can still be DOUBLE, for example), scale relative to NORMAL size and line attribute.*/
    ArrowHead(EArrowType t, bool block, double scale, const LineAttr &l) { Convert(t, block, XY(scale, scale), l); }
    /** Initializes using a single arrowhead type (which can still be DOUBLE, for example), scale relative to NORMAL size and line attribute.*/
    ArrowHead(EArrowType t, bool block, const XY &scale, const LineAttr &l) { Convert(t, block, scale, l); }
    /** Initializes using a series of arrowhead types (which can still be DOUBLE, for example), scale relative to NORMAL size and line attribute.*/
    ArrowHead(std::initializer_list<EArrowType> l, bool block, EArrowSize s = EArrowSize::SMALL, const LineAttr &line = LineAttr());

    bool Append(ESingleArrowType t, ESingleArrowSide s, bool o, const XY &sc, const LineAttr &l);
    /** Append an arrowhead to an existing series.*/
    bool Append(ESingleArrowType t, ESingleArrowSide s, bool o, double sc, const LineAttr &l)
        { return Append(t, s, o, XY(sc, sc), l); }
    /** Append an arrowhead to an existing series.*/
    bool Append(ESingleArrowType t, ESingleArrowSide s, bool o, EArrowSize sc, const LineAttr &l)
        { return Append(t, s, o, XY(ArrowScale(sc), ArrowScale(sc)), l); }
    /** Append an arrowhead to an existing series re-using the size and line of the last in the  Returns true on success.*/
    bool Append(ESingleArrowType t, ESingleArrowSide s= ESingleArrowSide::BOTH, bool o=false)
        { return size() && Append(t, s, o, back().scale, back().line); }
    /** Append an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Append(ESingleArrowType t, ESingleArrowSide s, bool o, const XY &sc)
        { return size() && Append(t, s, o, sc, back().line); }
    /** Append an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Append(ESingleArrowType t, ESingleArrowSide s, bool o, double sc)
        { return size() && Append(t, s, o, XY(sc, sc), back().line); }
    /** Append an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Append(ESingleArrowType t, ESingleArrowSide s, bool o, EArrowSize sc)
        { return size() && Append(t, s, o, XY(ArrowScale(sc), ArrowScale(sc)), back().line); }

    /** Append an arrowhead to an existing series re-using the size and line of the last in the  Returns true on success.*/
    bool Append(EArrowType t, bool block) { return size() && Append(t, block, back().scale, back().line); }
    /** Append an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Append(EArrowType t, bool block, EArrowSize s) { return size() && Append(t, block, XY(ArrowScale(s), ArrowScale(s)), back().line); }
    /** Append an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Append(EArrowType t, bool block, double scale) { return size() && Append(t, block, XY(scale, scale), back().line); }
    /** Append an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Append(EArrowType t, bool block, const XY &scale) { return size() && Append(t, block, scale, back().line); }
    /** Append an arrowhead to an existing or empty  Returns true on success, false on bad parameters.*/
    bool Append(EArrowType t, bool block, EArrowSize s, const LineAttr &l) { return Append(t, block, XY(ArrowScale(s), ArrowScale(s)), l); }
    bool Append(EArrowType t, bool block, const XY &scale, const LineAttr &l);

    bool Prepend(ESingleArrowType t, ESingleArrowSide s, bool o, const XY &sc, const LineAttr &l);
    /** Prepend an arrowhead to an existing series.*/
    bool Prepend(ESingleArrowType t, ESingleArrowSide s, bool o, double sc, const LineAttr &l)
        { return Prepend(t, s, o, XY(sc, sc), l); }
    /** Prepend an arrowhead to an existing series.*/
    bool Prepend(ESingleArrowType t, ESingleArrowSide s, bool o, EArrowSize sc, const LineAttr &l)
        { return Prepend(t, s, o, XY(ArrowScale(sc), ArrowScale(sc)), l); }
    /** Prepend an arrowhead to an existing series re-using the size and line of the last in the  Returns true on success.*/
    bool Prepend(ESingleArrowType t, ESingleArrowSide s= ESingleArrowSide::BOTH, bool o=false)
        { return size() && Prepend(t, s, o, back().scale, back().line); }
    /** Prepend an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Prepend(ESingleArrowType t, ESingleArrowSide s, bool o, const XY &sc)
        { return size() && Prepend(t, s, o, sc, back().line); }
    /** Prepend an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Prepend(ESingleArrowType t, ESingleArrowSide s, bool o, double sc)
        { return size() && Prepend(t, s, o, XY(sc, sc), back().line); }
    /** Prepend an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Prepend(ESingleArrowType t, ESingleArrowSide s, bool o, EArrowSize sc)
        { return size() && Prepend(t, s, o, XY(ArrowScale(sc), ArrowScale(sc)), back().line); }
    /** Prepend an arrowhead to an existing series re-using the size and line of the last in the  Returns true on success.*/
    bool Prepend(EArrowType t, bool block) { return size() && Prepend(t, block, back().scale, back().line); }
    /** Prepend an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Prepend(EArrowType t, bool block, EArrowSize s) { return size() && Prepend(t, block, XY(ArrowScale(s), ArrowScale(s)), back().line); }
    /** Prepend an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Prepend(EArrowType t, bool block, double scale) { return size() && Prepend(t, block, XY(scale, scale), back().line); }
    /** Prepend an arrowhead to an existing series re-using the line of the last in the  Returns true on success.*/
    bool Prepend(EArrowType t, bool block, const XY &scale) { return size() && Prepend(t, block, scale, back().line); }
    /** Prepend an arrowhead to an existing or empty  Returns true on success, false on bad parameters.*/
    bool Prepend(EArrowType t, bool block, EArrowSize s, const LineAttr &l) { return Prepend(t, block, XY(ArrowScale(s), ArrowScale(s)), l); }
    bool Prepend(EArrowType t, bool block, const XY &scale, const LineAttr &l);

    ArrowHead & operator += (const ArrowHead &o) { if (!o.IsNone()) { if (IsNone()) operator=(o); else { PrepToAdd(); if (o.series.size()) series.insert(series.end(), o.series.begin(), o.series.end()); else series.push_back(o.single); } } return *this; }
    ArrowHead operator + (const ArrowHead &o) const & { ArrowHead ret(*this);  return ret += o;  }
    ArrowHead operator + (const ArrowHead &o) && { return std::move(*this += o); }
    const SingleArrowHead& front() const { if (series.size()) return series.front(); return single; }
    const SingleArrowHead& back() const { if (series.size()) return series.back(); return single; }
    size_t size() const { if (series.size()) return series.size(); return single.type!=ESingleArrowType::NONE; }
    void clear() { if (series.size()) return series.clear(); single.type=ESingleArrowType::NONE; }
    const SingleArrowHead& at(size_t u) const { return u==0 && series.size()==0 ? single : series.at(u); }
    SingleArrowHead& at(size_t u) { return u==0 && series.size()==0 ? single : series.at(u); }

    static bool ParseGraphviz(std::string_view attr, EArcArrowType at, LineAttr line=LineAttr(), XY scale=XY(0,0),
                              const ColorSet *colors=nullptr, FileLineCol line_pos= FileLineCol(), Chart *chart=nullptr,
                              ArrowHead *arrow=nullptr, std::string *replaceto= nullptr);

    /** @name Functions for line arrowheads.
     * @{ */
    WHRetval WidthHeight(const LineAttr &mainline_before, const LineAttr &mainline_after) const;
    /** True if the arrowhead starts with (or entirely consists of) a symmetric element or is totally empty.*/
    bool StartsWithSymmetricOrNone() const { return ::IsSymmetricOrNone(front().type); }
    /** True if the arrowhead starts with (or entirely consists of) a symmetric element and is not totally empty.*/
    bool StartsWithSymmetricAndNotNone() const { return ::IsSymmetricAndNotNone(front().type); }
    /** True if the arrowhead consists of a symmetric element or is totally empty.*/
    bool IsSymmetricOrNone() const { return ::IsSymmetricOrNone(front().type); }
    /** True if the arrowhead consists of a symmetric element and is not totally empty.*/
    bool IsSymmetricAndNotNone() const { return size()==1 && ::IsSymmetricAndNotNone(front().type); }
    /** True if the arrowhead is totally empty.*/
    bool IsNone() const { return series.size()==0 && single.type==ESingleArrowType::NONE; }

    Contour TargetCover(const XY &xy, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after) const;
    Contour ClipForLine(const XY &xy, bool flip, bool ignore_opaque, const LineAttr &mainline_before, const LineAttr &mainline_after, bool mirror = false) const;
    Contour Cover(const XY &xy, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool mirror=false) const;
    void Draw(Canvas &canvas, const XY &xy, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool mirror = false) const;

    Contour TargetCover(const XY &xy, const XY &from, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after) const;
    Contour ClipForLine(const XY &xy, const XY &from, bool ignore_opaque, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool mirror = false) const;
    Contour Cover(const XY &xy, const XY &from, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool mirror = false) const;
    void Draw(Canvas &canvas, const XY &xy, const XY &from, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool mirror = false) const;

    /** Return the area for which the target (e.g., an entity line) shall not be drawn.
     * This version of the function assumes the arrowhead is drawn to
     * the end (or start) of an arbitrary path.
     * @param [in] path The tip of the arrow is at the end/start of this path.
     * @param [in] flip If true, the arrowhead needs to be flipped to the arrow line.
     *                  useful for the 'half' style, which is not symmetric to the arrow line.
     * @param [in] mainline_before The line style of the path.
     * @param [in] mainline_after If the line continues beyond, that line style.
     * @param [in] at_end If true the arrow is drawn at the end of the path, else the beginning (in reverse).
     * @param [in] mirror Set it to true if this is a mirrored version of an already drawn arrowhead.
     * @param [in] cache If the covers of the arrowhead were cached, supplyng them will speed up operation.
     * @returns The area, where the target shall not be drawn.*/
    Contour TargetCover(const Path &path, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool at_end = true, bool mirror = false, const ArrowHeadCoverCache *cache = nullptr) const
        { return TargetCover(path, PathPos(at_end ? path.size()-1 : 0, at_end ? 1 : 0), at_end, flip, mainline_before, mainline_after, mirror, cache); }
    /** Return the area where the arrow line shall not be drawn.
     * This version of the function assumes the arrowhead is drawn to
     * the end (or start) of an arbitrary path.
     * @param [in] path The tip of the arrow is at the end/start of this path.
     * @param [in] flip If true, the arrowhead needs to be flipped to the arrow line.
     *                  useful for the 'half' style, which is not symmetric to the arrow line.
     * @param [in] ignore_opaque When set to true, we will not clip the line for filled arrowheads.
     *                           This may lead to better performance if you draw the arrowhead
     *                           *after* the arrow line and the fill color is opaque.
     * @param [in] mainline_before The line style of the path.
     * @param [in] mainline_after If the line continues beyond, that line style.
     * @param [in] at_end If true the arrow is drawn at the end of the path, else the beginning (in reverse).
     * @param [in] mirror Set it to true if this is a mirrored version of an already drawn arrowhead.
     * @param [in] cache If the covers of the arrowhead were cached, supplyng them will speed up operation.
     * @returns The area, where the arrow line shall not be drawn.*/
    Contour ClipForLine(const Path &path, bool flip, bool ignore_opaque, const LineAttr &mainline_before, const LineAttr &mainline_after, bool at_end = true, bool mirror = false, const ArrowHeadCoverCache *cache = nullptr) const
        { return ClipForLine(path, PathPos(at_end ? path.size()-1 : 0, at_end ? 1 : 0), at_end, flip, ignore_opaque, mainline_before, mainline_after, mirror, cache); }
    /** Return the area covered by the arrowhead.
     * This version of the function assumes the arrowhead is drawn to
     * the end (or start) of an arbitrary path.
     * @param [in] path The tip of the arrow is at the end/start of this path.
     * @param [in] flip If true, the arrowhead needs to be flipped to the arrow line.
     *                  useful for the 'half' style, which is not symmetric to the arrow line.
     * @param [in] mainline_before The line style of the path.
     * @param [in] mainline_after If the line continues beyond, that line style.
     * @param [in] at_end If true the arrow is drawn at the end of the path, else the beginning (in reverse).
     * @param [in] mirror Set it to true if this is a mirrored version of an already drawn arrowhead.
     * @param [out] cache If a non-nullptr cache object is given, it is filled with individual covers and
     *                    positions to be used by TargetCover(), ClipForLine() and Draw().
     * @returns The area, covered by the arrowhead.*/
    Contour Cover(const Path &path, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool at_end = true, bool mirror = false, ArrowHeadCoverCache *cache = nullptr) const
        { return Cover(path, PathPos(at_end ? path.size()-1 : 0, at_end ? 1 : 0), at_end, flip, mainline_before, mainline_after, mirror, cache); }
    /** Draws the arrowhead.
     * This version of the function assumes the arrowhead is drawn to
     * the end (or start) of an arbitrary path.
     * @param canvas The canvas to draw onto.
     * @param [in] path The tip of the arrow is at the end/start of this path.
     * @param [in] flip If true, the arrowhead needs to be flipped to the arrow line.
     *                  useful for the 'half' style, which is not symmetric to the arrow line.
     * @param [in] mainline_before The line style of the path.
     * @param [in] mainline_after If the line continues beyond, that line style.
     * @param [in] at_end If true the arrow is drawn at the end of the path, else the beginning (in reverse).
     * @param [in] mirror Set it to true if this is a mirrored version of an already drawn arrowhead.
    * @param[in] cache If the covers of the arrowhead were cached, supplyng them will speed up operation.*/
    void Draw(Canvas &canvas, const Path &path, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool at_end = true, bool mirror = false, const ArrowHeadCoverCache *cache = nullptr) const
        { Draw(canvas, path, PathPos(at_end ? path.size()-1 : 0, at_end ? 1 : 0), at_end, flip, mainline_before, mainline_after, mirror, cache); }

    Contour TargetCover(const Path &path, const PathPos &pos, bool forward, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool mirror = false, const ArrowHeadCoverCache *cache = nullptr) const;
    Contour ClipForLine(const Path &path, const PathPos &pos, bool forward, bool flip, bool ignore_opaque, const LineAttr &mainline_before, const LineAttr &mainline_after, bool mirror = false, const ArrowHeadCoverCache *cache = nullptr) const;
    Contour Cover(const Path &path, const PathPos &pos, bool forward, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool mirror = false, ArrowHeadCoverCache *cache=nullptr) const;
    void Draw(Canvas &canvas, const Path &path, const PathPos &pos, bool forward, bool flip, const LineAttr &mainline_before, const LineAttr &mainline_after, bool mirror = false, const ArrowHeadCoverCache *cache = nullptr) const;
    /** @} */

    /** @name Functions for block arrowheads.
     * Since they can only contain a single segment, usually just forwarding to the
     * corresponding function in SingleArrowHead.
     * @{ */
     /** True if the arrowhead consists of a symmetric element and is not totally empty.*/
    bool BreaksBlockArrow() const { _ASSERT(size()<=1); return ::BreaksBlockArrow(front().type); }
    /** Return the detailed dimensions of this arrowhead. See SingleArrowHead::BlockWidthHeight(). */
    SingleArrowHead::BlockWHRetval BlockWidthHeight(double body_height, const LineAttr & line, bool middle) const { _ASSERT(size()<2); return front().BlockWidthHeight(body_height, line, middle);}
    /** Return margin required for text inside a block arrow. See SingleArrowHead::BlockTextMargin(). */
    double BlockTextMargin(const Contour &text_cover, bool left_margin, double sy, double dy, const LineAttr & line) const { _ASSERT(series.size()<2); return front().BlockTextMargin(text_cover, left_margin, sy, dy, line); }
    /** Return outer edge of a block arrowhead. See SingleArrowHead::BlockContour(). */
    Contour BlockContour(double x, double sy, double dy, const LineAttr & line, bool middle) const { _ASSERT(series.size()<2); return front().BlockContour(x, sy, dy, line, middle); }
    /** Return additional lines to draw if the arrowhead is in the middle of an arrow. See SingleArrowHead::BlockMidPath(). */
    Path BlockMidPath(double x, double sy, double dy, const LineAttr & line) const { _ASSERT(series.size()<2); return front().BlockMidPath(x, sy, dy, line); }
    /** @} */
};

/** A class containing full arrowhead info for an arrow with a single (tail) arrowhead. */
class OneArrowHead : public OneArrowAttr
{
public:
    mutable ArrowHead endArrowHead;   ///<The actual ending arrowhead (calculated from size, xmul, ymul and endType)
    using OneArrowAttr::OneArrowAttr;
    void CreateArrowHeads(double scale = 1) const {
        _ASSERT(OneArrowAttr::IsComplete());
        if (dirty)
        { CreateAnArrowHead(endArrowHead, scale, endType, gvEndType); dirty = false; }
    }
    const ArrowHead& GetArrowHead(bool /*bidir*/, EArrowEnd which) const noexcept { _ASSERT(!dirty); return which==EArrowEnd::END ? endArrowHead : ArrowHead::noneArrowHead; }

    /** Returns what area we cover from the target at the end (e.g., an entity line).
     * (Used only for symmetric empty arrowheads, others return empty.)
     * @param [in] path The path with the arrowhead at its end.
     * @param [in] line The line style of the path.
     * @returns the area, where background should show instead of the target.*/
    Contour TargetCoverEnd(const Path &path, const LineAttr &line) const
        { _ASSERT(!dirty); return endArrowHead.TargetCover(path, false, line, line); }
    /** Returns what area we cover from the arrow line (i.e., 'path').
     * @param [in] path The path with the arrowhead at its end.
     * @param [in] line The line style of the path.
     * @param [in] ignore_opaque If true, the caller will draw the arrowhead *after* the
     *                           arrow line and thus opaque, filled arrowheads does not
     *                           have to return clip - a performance optimization.
     * @returns the area, where background should show instead of the arrow line.*/
    Contour ClipForLine(const Path &path, const LineAttr &line, bool ignore_opaque=false) const
        { _ASSERT(!dirty); return endArrowHead.ClipForLine(path, false, ignore_opaque, line, line); }
    Path ClipLine(const Path &path, const LineAttr &line) const;
    /** Returns what area the arrowhead covers.
     * @param [in] path The path with the arrowhead at its end.
     * @param [in] line The line style of the path.
     * @returns the area of coverage.*/
    Contour Cover(const Path &path, const LineAttr &line) const
        { _ASSERT(!dirty); return endArrowHead.Cover(path, false, line, line); }
    /** Draws the arrowhead
     * @param canvas The canvas to draw onto.
     * @param [in] path The path with the arrowhead at its end.
     * @param [in] line The line style of the path.*/
    void Draw(Canvas &canvas, const Path &path, const LineAttr &line) const
        { _ASSERT(!dirty); endArrowHead.Draw(canvas, path, false, line, line); }
};



/** A class containing full arrowhead info for an arrow at a specific path with
 * a single (tail) arrowhead. */
class OneArrowHeadInstance
{
protected:
    mutable ArrowHeadCoverCache endArrowHeadCache; ///<The cache storing the computed covers;
    mutable bool cache_computed = false;///<True if the end arrowhead cache is computed
    void ComputeCache(const OneArrowHead &ah, const Path &path, const LineAttr &line) const { if (!cache_computed) Cover(ah, path, line); }
public:
    OneArrowHeadInstance() = default;

    /** Returns what area we cover from the target at the end (e.g., an entity line).
    * (Used only for symmetric empty arrowheads, others return empty.)
    * @param [in] ah The ArrowHead this instance is associated with
    * @param [in] path The path with the arrowhead at its end.
    * @param [in] line The line style of the path.
    * @returns the area, where background should show instead of the target.*/
    Contour TargetCoverEnd(const OneArrowHead &ah, const Path &path, const LineAttr &line) const {
        ComputeCache(ah, path, line);
        return ah.endArrowHead.TargetCover(path, false, line, line, true, false, &endArrowHeadCache);
    }
    /** Returns what area we cover from the arrow line (i.e., 'path').
    * @param [in] ah The ArrowHead this instance is associated with
    * @param [in] path The path with the arrowhead at its end.
    * @param [in] line The line style of the path.
    * @param [in] ignore_opaque If true, the caller will draw the arrowhead *after* the
    *                           arrow line and thus opaque, filled arrowheads does not
    *                           have to return clip - a performance optimization.
    * @returns the area, where background should show instead of the arrow line.*/
    Contour ClipForLine(const OneArrowHead &ah, const Path &path, const LineAttr &line, bool ignore_opaque = false) const {
        ComputeCache(ah, path, line);
        return ah.endArrowHead.ClipForLine(path, false, ignore_opaque, line, line, true, false, &endArrowHeadCache);
    }
    /** Returns what area the arrowhead covers.
    * @param [in] ah The ArrowHead this instance is associated with
    * @param [in] path The path with the arrowhead at its end.
    * @param [in] line The line style of the path.
    * @returns the area of coverage.*/
    Contour Cover(const OneArrowHead &ah, const Path &path, const LineAttr &line) const;
    /** Draws the arrowhead
    * @param canvas The canvas to draw onto.
    * @param [in] ah The ArrowHead this instance is associated with
    * @param [in] path The path with the arrowhead at its end.
    * @param [in] line The line style of the path.*/
    void Draw(const OneArrowHead &ah, Canvas &canvas, const Path &path, const LineAttr &line) const
    {
        ComputeCache(ah, path, line);
        ah.endArrowHead.Draw(canvas, path, false, line, line, true, false, &endArrowHeadCache);
    }
};


/** A class containing full arrowhead info for an arrow including start and end arrowhead (head and tail)*/
class TwoArrowHeads : public TwoArrowAttr
{
public:
    mutable ArrowHead endArrowHead;   ///<The actual ending arrowhead (calculated from size, xmul, ymul and endType)
    mutable ArrowHead startArrowHead; ///<The actual starting arrowhead (calculated from size, xmul, ymul and startType)
    using TwoArrowAttr::TwoArrowAttr;
    void CreateArrowHeads(double scale = 1) const {
        _ASSERT(TwoArrowAttr::IsComplete());
        if (dirty) {
            CreateAnArrowHead(endArrowHead, scale, endType, gvEndType);
            CreateAnArrowHead(startArrowHead, scale, startType, gvStartType);
            dirty = false;
        }
    }

    TwoArrowHeads() = default;
    const ArrowHead& GetArrowHead(bool bidir, EArrowEnd which) const noexcept { _ASSERT(!dirty); return which==EArrowEnd::END ? endArrowHead : which != EArrowEnd::END ? ArrowHead::noneArrowHead : bidir ? endArrowHead : startArrowHead; }
    /** Returns what area we cover from the target at the start (e.g., an entity line).
    * (Used only for symmetric empty arrowheads, others return empty.)
    * @param [in] path The path with the arrowhead at its start.
    * @param [in] line The line style of the path.
    * @returns the area, where background should show instead of the target.*/
    Contour TargetCoverStart(const Path &path, const LineAttr &line) const
    { _ASSERT(!dirty); return startArrowHead.TargetCover(path, true, line, line, false); }
    /** Returns what area the arrowheads cover from the arrow line (i.e., 'path').
    * @param [in] path The path with the arrowhead at its start and end.
    * @param [in] line The line style of the path.
    * @param [in] ignore_opaque If true, the caller will draw the arrowhead *after* the
    *                           arrow line and thus opaque, filled arrowheads does not
    *                           have to return clip - a performance optimization.
    * @returns the area, where background should show instead of the arrow line.*/
    Contour ClipForLine(const Path &path, const LineAttr &line, bool ignore_opaque = false) const
    { _ASSERT(!dirty); return endArrowHead.ClipForLine(path, false, ignore_opaque, line, line) + startArrowHead.ClipForLine(path, true, ignore_opaque, line, line, false); }
    Path ClipLine(const Path &path, const LineAttr &line) const;
    /** Returns what area the arrowheads cover.
    * @param [in] path The path with the arrowhead at its start and end.
    * @param [in] line The line style of the path.
    * @returns the area of coverage.*/
    Contour Cover(const Path &path, const LineAttr &line) const
    { _ASSERT(!dirty); return endArrowHead.Cover(path, false, line, line) + startArrowHead.Cover(path, true, line, line, false); }
    /** Draws the arrowheads
    * @param canvas The canvas to draw onto.
    * @param [in] path The path with the arrowheads at its start and end.
    * @param [in] line The line style of the path.*/
    void Draw(Canvas &canvas, const Path &path, const LineAttr &line) const
    { _ASSERT(!dirty); endArrowHead.Draw(canvas, path, false, line, line); startArrowHead.Draw(canvas, path, true, line, line, false); }
};

/** A class containing full arrowhead info for an arrow at a specific path and position
 * including start and end arrowhead (head and tail)*/
class TwoArrowHeadsInstance
{
protected:
    mutable ArrowHeadCoverCache startArrowHeadCache; ///<The cache storing the computed covers;
    mutable ArrowHeadCoverCache endArrowHeadCache; ///<The cache storing the computed covers;
    mutable bool cache_computed = false;///<True if the end arrowhead cache is computed
    void ComputeCache(const TwoArrowHeads &ah, const Path &path, const LineAttr &line) const { if (!cache_computed) Cover(ah, path, line); }
public:
    TwoArrowHeadsInstance() = default;

    /** Returns what area we cover from the target at the start (e.g., an entity line).
     * (Used only for symmetric empty arrowheads, others return empty.)
     * @param [in] ah The ArrowHead this instance is associated with
     * @param [in] path The path with the arrowhead at its start.
     * @param [in] line The line style of the path.
     * @returns the area, where background should show instead of the target.*/
    Contour TargetCoverStart(const TwoArrowHeads &ah, const Path &path, const LineAttr &line) const {
        ComputeCache(ah, path, line);
        return ah.startArrowHead.TargetCover(path, true, line, line, false, false, &startArrowHeadCache);
    }
    /** Returns what area we cover from the target at the end (e.g., an entity line).
     * (Used only for symmetric empty arrowheads, others return empty.)
     * @param [in] ah The ArrowHead this instance is associated with
     * @param [in] path The path with the arrowhead at its end.
     * @param [in] line The line style of the path.
     * @returns the area, where background should show instead of the target.*/
    Contour TargetCoverEnd(const TwoArrowHeads &ah, const Path &path, const LineAttr &line) const {
        ComputeCache(ah, path, line);
        return ah.endArrowHead.TargetCover(path, false, line, line, true, false, &endArrowHeadCache);
    }
    /** Returns what area the arrowheads cover from the arrow line (i.e., 'path').
     * @param [in] ah The ArrowHead this instance is associated with
     * @param [in] path The path with the arrowhead at its start and end.
     * @param [in] line The line style of the path.
     * @param [in] ignore_opaque If true, the caller will draw the arrowhead *after* the
     *                           arrow line and thus opaque, filled arrowheads does not
     *                           have to return clip - a performance optimization.
     * @returns the area, where background should show instead of the arrow line.*/
    Contour ClipForLine(const TwoArrowHeads &ah, const Path &path, const LineAttr &line, bool ignore_opaque = false) const
    {
        ComputeCache(ah, path, line);
        return ah.endArrowHead.ClipForLine(path, false, ignore_opaque, line, line, true, false, &endArrowHeadCache) +
               ah.startArrowHead.ClipForLine(path, true, ignore_opaque, line, line, false, false, &startArrowHeadCache);
    }
    Path ClipLine(const TwoArrowHeads &ah, const Path &path, const LineAttr &line) const { return ah.ClipLine(path, line); }
    /** Returns what area the arrowheads cover.
     * @param [in] ah The ArrowHead this instance is associated with
     * @param [in] path The path with the arrowhead at its start and end.
     * @param [in] line The line style of the path.
     * @returns the area of coverage.*/
    Contour Cover(const TwoArrowHeads &ah, const Path &path, const LineAttr &line) const;
    /** Draws the arrowheads
     * @param canvas The canvas to draw onto.
     * @param [in] ah The ArrowHead this instance is associated with
     * @param [in] path The path with the arrowheads at its start and end.
     * @param [in] line The line style of the path.*/
    void Draw(const TwoArrowHeads &ah, Canvas &canvas, const Path &path, const LineAttr &line) const {
        ah.endArrowHead.Draw(canvas, path, false, line, line, true, false, &endArrowHeadCache);
        ah.startArrowHead.Draw(canvas, path, true, line, line, false, false, &startArrowHeadCache);
    }
};

/** Provides coverage for both path and arrowheads together
 * We only include the arrowheads at the end of the path, no mid-arrowheads
 * even if the supplied 'ah' argument has them.
 * @param ah Either a OneArrowhead or a TwoArrowHeads (or descendant, scuh as
 *           OneArrowheadInstance or a TwoArrowHeadsInstance) class
 * @param [in] path The path with the arrowhead at its (start and) end.
 * @param [in] line The line style of the path.*/
template <class ArrowHeads>
Contour CoverPathWithArrows(ArrowHeads &ah, const Path &path, const LineAttr &line)
{
    Contour ret = path.SimpleWiden(line.LineWidth()/2);
    ret -= ah.ClipForLine(path, line);
    ret += ah.Cover(path, line).SetVisible(true);
    return ret;
}

/** Provides coverage for both path and arrowheads together
 * We only include the arrowheads at the end of the path, no mid-arrowheads
 * even if the supplied 'ah' argument has them.
 * @param ahi Either a OneArrowheadInstance or a TwoArrowHeadsInstance class
 * @param ah The corresponding ArrowHead
 * @param [in] path The path with the arrowhead at its (start and) end.
 * @param [in] line The line style of the path.*/
template <class ArrowHeadsInstance, class ArrowHeads>
Contour CoverPathWithArrows(const ArrowHeadsInstance &ahi, const ArrowHeads &ah, const Path &path, const LineAttr &line)
{
    Contour ret = path.SimpleWiden(line.LineWidth()/2);
    ret -= ahi.ClipForLine(ah, path, line);
    ret += ahi.Cover(ah, path, line).SetVisible(true);
    return ret;
}


/** A class containing full arrowhead info for an arrow including start and end arrowhead (head and tail),
 * but also arrowheads for mid-arrow stops and for locations where the arrow crosses, but does not stop
 * at another line (it skips that stop or 'jumps over' the other line).
 * For more examples of how this can be used, see libmscgen.*/
class FullArrowHeads : public FullArrowAttr
{
public:
    mutable ArrowHead endArrowHead;   ///<The actual end arrowhead (calculated from size, xmul, ymul and midType)
    mutable ArrowHead startArrowHead; ///<The actual start arrowhead (calculated from size, xmul, ymul and skipType)
    mutable ArrowHead midArrowHead;   ///<The actual middle arrowhead (calculated from size, xmul, ymul and midType)
    mutable ArrowHead skipArrowHead;  ///<The actual skipping arrowhead (calculated from size, xmul, ymul and skipType)

    using FullArrowAttr::FullArrowAttr;
    void CreateArrowHeads(double scale = 1) const {
        _ASSERT(FullArrowAttr::IsComplete());
        if (dirty) {
            if (type!=EArcArrowType::BIGARROW)
                CreateAnArrowHead(skipArrowHead, scale, skipType, gvSkipType);
            CreateAnArrowHead(endArrowHead, scale, endType, gvEndType);
            CreateAnArrowHead(startArrowHead, scale, startType, gvStartType);
            CreateAnArrowHead(midArrowHead, scale, midType, gvMidType);
            dirty = false;
        }
    }

    FullArrowHeads() = default;
    /** Return a reference to the arrowhead specified in 'which'. If bidir is true then we return 'endArrowHead' even for which==START. */
    ArrowHead &GetArrowHead(bool bidir, EArrowEnd which) noexcept { _ASSERT(!dirty); return which==EArrowEnd::END ? endArrowHead : which==EArrowEnd::MIDDLE ? midArrowHead : which==EArrowEnd::SKIP ? skipArrowHead : bidir ? endArrowHead : startArrowHead; }
    /** Return a reference to the arrowhead specified in 'which'. If bidir is true then we return 'endArrowHead' even for which==START. */
    const ArrowHead &GetArrowHead(bool bidir, EArrowEnd which) const noexcept { _ASSERT(!dirty);  return which==EArrowEnd::END ? endArrowHead : which==EArrowEnd::MIDDLE ? midArrowHead : which==EArrowEnd::SKIP ? skipArrowHead : bidir ? endArrowHead : startArrowHead; }
    const ArrowHead &GetArrowHead(bool forward, bool bidir, EArrowEnd which, bool left) const noexcept;
};

#endif //ARROWHEAD_H
