/*
    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 msc.h The declaration of the msc class describing a chart.
 * @ingroup libmscgen_files */
#if !defined(MSC_H)
#define MSC_H

#include <cassert>
#include <string>
#include <cstring>
#include <sstream>
#include <list>
#include <map>
#include <set>
#include <vector>
#include <unordered_map>
#include "chartbase.h"
#include "mscstyle.h"
#include "mscentity.h"
#include "cgen_progress.h"
#include "commands.h" //includes also arcs.h boxes.h

/** This is the namespace containing signalling chart elements, except parsing.*/
namespace msc {

/** Name of the virtual entity representing the ultimate left of the chart (of left comments), but notes may extend leftward. */
#define START_ENT_STR  "(start)"
/** Name of the virtual entity representing the line separating the left side comments from the chart body. */
#define LNOTE_ENT_STR "(leftside_note)"
/** Name of the virtual entity representing the left side of the chart body (some margin away from LNOTE_ENT_STR). */
#define LSIDE_ENT_STR "(leftside)"
/** Name of the virtual entity representing the right side of the chart body (some margin away from RNOTE_ENT_STR). */
#define RSIDE_ENT_STR "(rightside)"
/** Name of the virtual entity representing the line separating the right side comments from the chart body. */
#define RNOTE_ENT_STR "(rightside_note)"
/** Name of the virtual entity representing the ultimate right side of the chart (of left comments), but notes may extend rightward*/
#define END_ENT_STR "(end)"
/** Name used for entity, when a pipe symbol is specified to the left.
 * Only used in a call to Msc::CreateArrow() or Arrow::AddSegment().
 * Those functions will know what to do with it.*/
#define LSIDE_PIPE_ENT_STR "(left_side_pipe)"
 /** Name used for entity, when a pipe symbol is specified to the left.
 * Only used in a call to Msc::CreateArrow() or Arrow::AddSegment().
 * Those functions will know what to do with it.*/
#define RSIDE_PIPE_ENT_STR "(right_side_pipe)"

 /** Name of a virtual marker representing the current vertical location. Used in verticals when one end is not specified.*/
#define MARKER_HERE_STR "\""
/** Name of the builtin chart.top marker */
#define MARKER_BUILTIN_CHART_TOP_STR "chart.top"
/** Name of the builtin chart.bottom marker */
#define MARKER_BUILTIN_CHART_BOTTOM_STR "chart.bottom"

/** The margin on left and right side, between LNote-LSide and RSide-RNote.
 * In pos space, will be fed into XCoord().*/
#define MARGIN 0.1
/** The default size of the comment area in `pos` space if
 * - we have comments
 * - we have no 'hspace comment' commands and
 * - all comments are wrapped */
#define DEFAULT_COMMENT_SIZE 1


/////////////////////////////////////////////////////////////


/** A virtual entity index, used when we represent a distance required at the left side of an entity. */
#define DISTANCE_LEFT -1
/** A virtual entity index, used when we represent a distance required at the right side of an entity. */
#define DISTANCE_RIGHT -2

/** A pair of entity indexes for the purpose of later association with a required distance between them.*/
class IPair {
public:
    unsigned first; ///<Index of the left entity.
    unsigned second;///<Index of the right entity.
    IPair(unsigned e1, unsigned e2) : first(std::min(e1,e2)), second(std::max(e1,e2)) {}
    /** Calculates the difference between the two index values*/
    unsigned Diff() const {return second-first;}
    bool operator == (const IPair &o) const { return first==o.first && second == o.second; }
    /** Ordering for IPairs are: the closer the two entities are the smaller the IPair, else ordered by the first entity.*/
    bool operator<(const IPair &ei2) const {if (Diff()==ei2.Diff()) return first < ei2.first; return Diff()<ei2.Diff();}
};

namespace map_manip {
    template <typename key, typename comp1, typename comp2>
    void MergeMax(std::map<key, double, comp1> &base, const std::map<key, double, comp2> &add)
    {
        for (auto &j : add) {
            auto i = base.find(j.first);
            if (i==base.end())
                base[j.first] = j.second;
            else
                i->second = std::max(i->second, j.second);
        }
    }

    template <typename key, typename comp1, typename comp2>
    void MergeAdd(std::map<key, double, comp1> &base, const std::map<key, double, comp2> &add)
    {
        for (auto &j : add) {
            auto i = base.find(j.first);
            if (i==base.end())
                base[j.first] = j.second;
            else
                i->second = i->second + j.second;
        }
    }

    template <typename key, typename comp1>
    void Insert(std::map<key, double, comp1> &base, const key &k, double d)
    {
        auto i = base.find(k);
        if (i==base.end()) base[k] = d;
        else i->second = std::max(i->second, d);
    }

    template <typename key, typename comp1>
    bool IncreaseIfNonZero(std::map<key, double, comp1> &base, const key &k, double d)
    {
        auto i = base.find(k);
        if (i==base.end()) return false;
        i->second += d;
        return true;
    }

    template <typename key, typename comp1>
    double Query(const std::map<key, double, comp1> &base, const key &k)
    {
        auto i = base.find(k);
        return i==base.end() ? 0 : i->second;
    }
}


/** Stores three types of horizontal distance requirements between entities.
 * One such object holds the collected horizontal distance requirements from a part
 * (called 'section') of the chart. A section is a point in the chart, distance requirements
 * are valid from that point downward - till the next section. The top of the section
 * can be either a marker or the top/bottom edges of a chart element.
 * These sections are collected in class DistanceRequirements.
 *
 * Entities are identified here by their index (an enumeration from left to right, with
 * zero being the StartEntity (the leftmost) and 1 being the virtual entity representing the
 * line between the leftside comments and the chart body. The largest index shall be
 * of the virtual entity representing the line between the right side comments and the
 * chart body.
 *
 * We store three types of distance requirements
 * - Between pairs of entities (e.g., a label on an arrow between them)
 *   (not necessarily neighbouring entities)
 * - On the left or right side of entities
 *   (e.g., entitiy headings, box/pipe sides, arrowheads expanding beyond the entity line)
 *   Note that all entities shall report side distance requirements that already take entity
 *   activation into account.
 *
 * For each section, we also store which was the leftmost and rightmost entities used in that
 * section - so that we can place verticals automatically left/right of a section. */
class DistanceRequirementsSection
{
    friend class DistanceRequirements;
    const std::string          marker;  ///<The name of the marker after which we store. Empty for top of chart. If equal to MARKER_HERE_STR, 'element' and 'top' specifies the position.
    const MscElement * const   element; ///<If we are not aligned to a marker but to an element, this is the one.
    const bool                 top;     ///<If we are aligned to an element, this tells if we are aligned to its top or bottom.
    std::map<unsigned, double> left;    ///<Space requirements on the left side of entities
    std::map<unsigned, double> right;   ///<Space requirements on the right side of entities
    std::map<IPair, double>    pairs;   ///<Space requirements between arbitrary (potentially non-neighbouring) pairs of entities
    unsigned                   left_entity;  ///<The index of the leftmost entity touched
    unsigned                   right_entity; ///<The index of the rightmost entity touched
public:
    enum { NO_LEFT_ENTITY = 10000, NO_RIGHT_ENTITY = 0 };
    /** Creates a new vertical position for a marker, zeroing the distance requirements after.*/
    explicit DistanceRequirementsSection(const std::string &m = "") : marker(m), element(nullptr), top(false), left_entity(NO_LEFT_ENTITY), right_entity(NO_RIGHT_ENTITY){}
    /** Creates a new vertical position for the top/bottom edge of an element, zeroing the distance requirements after.*/
    explicit DistanceRequirementsSection(const MscElement *e, bool t) : marker(MARKER_HERE_STR), element(e), top(t), left_entity(NO_LEFT_ENTITY), right_entity(NO_RIGHT_ENTITY) {}
    DistanceRequirementsSection(DistanceRequirementsSection&&) = default;
    DistanceRequirementsSection(const DistanceRequirementsSection&) = delete;
    /** Return the set of pairwise distances. Ordered by entity pair distance.*/
    const std::map<IPair, double> &GetPairs() const { return pairs; }
    /** Return the set of left distances. Ordered by entities distance.*/
    const std::map<unsigned, double> &GetLeft() const { return left; }
    /** Return the set of right distances. Ordered by entities distance.*/
    const std::map<unsigned, double> &GetRight() const { return right; }

    void Insert(unsigned e1, int e2, double d);
    bool IncreaseIfNonZero(unsigned e1, int e2, double d);
    double Query(unsigned e1, int e2) const; ///<Return a distance req
    /** Increase an existing distance requirement if already nonzero.
     * Returns true, if an increase has been made. */
    /** Indicate that a certain entity is touched */
    void InsertEntity(unsigned e) { left_entity = std::min(left_entity, e); right_entity = std::max(right_entity, e); }
    unsigned QueryLeftEntity() const { return left_entity; } ///<Returns the leftmost touched entity, NO_LEFT_ENTITY if none
    unsigned QueryRightEntity() const { return right_entity; } ///<Returns the righmost touched entity, NO_RIGHT_ENTITY if none
    DistanceRequirementsSection &operator +=(const DistanceRequirementsSection &d);
    void CombineLeftRightToPair_Add(double gap);
};

/** A collection storing horizontal distance requirements between various
 * entities in the chart. The chart is divided into 'sections' and distance
 * requirements are defined for a given section. Then the final positioning
 * of entities (and boxes and verticals) are composed from merging the
 * requirements in sections overlapping with the box or vertical or by
 * merging requirements from all sections for the entity positioning.
 * Since this is used during the Width() pass across the arcs, we no not
 * konw y coordinates yet. So sections are defined and ordered by the order
 * of elements in the input file. This may differ from the y order in the
 * laid out chart (due to parallel mechanism), but this we cannot avoid.
 * Sections are defined by markers or top/bottom edges of chart elements.
 * 'Add' functions add a new section, 'Get' functions get iterators to
 * walk sections. 'Insert' functions insert a new distance requirement
 * into one or more sections, 'Query' functions query requirements inserted
 * prior.*/
class DistanceRequirements
{
protected:
    /** A list containing vertical positions and the horizontal
     * space requirements just after this position, which are valid
     * until the next vertical position in the list.
     * Vertical positions are not identified by y coordinates,
     * since we determine horizontal scaling before y layout, and this
     * mechanism is needed to manage horizontal scaling. Instead, we
     * use markers and chart elements as they appear in the source
     * file, so vertical positions are akin to input file locations here.
     * This is OK without parallel blocks, but may such with them.
     * Anyway, this is a complicated enough algoritm already, be my
     * guest to do better.*/
    std::list<DistanceRequirementsSection> elements;
    std::map<std::string, MscElement *> marker_translations;
    std::map<IPair, double> hspace_pairs;
public:
    bool had_l_comment; ///<We had comments on the left side actually showing
    bool had_r_comment; ///<We had comments on the right side actually showing
    typedef std::list<DistanceRequirementsSection>::iterator iterator;
    typedef std::list<DistanceRequirementsSection>::const_iterator const_iterator;
    DistanceRequirements() : had_l_comment(false), had_r_comment(false) { elements.emplace_back(""); }
    /** Return the set of pairwise hspace distances. Ordered by entity pair distance.*/
    const std::map<IPair, double> &GetHSpacePairs() const { return hspace_pairs; }
    /** Insert a side distance, which happened after the last marker*/
    void Insert(unsigned e1, int e2, double d) { elements.back().Insert(e1, e2, d); }
    /** If the distance is nonzero, increase it by 'd' and return true.*/
    bool IncreaseIfNonZero(unsigned e1, int e2, double d) { return elements.back().IncreaseIfNonZero(e1, e2, d); }
    /** Insert distance requirement explicitly stated by the user via hspace.*/
    void InsertHSpace(unsigned e1, int e2, double d) { map_manip::Insert(hspace_pairs, IPair(e1, e2), d); }
    /** If the distance explicitly stated by the user via hspace is nonzero, increase it by 'd' and return true.*/
    bool IncreaseIfNonZeroHSpace(unsigned e1, int e2, double d) { return map_manip::IncreaseIfNonZero(hspace_pairs, IPair(e1, e2), d); }
    /** Indicate that an entity is touched after the last marker */
    void InsertEntity(unsigned e) { elements.back().InsertEntity(e); }
    /** Indicate that an entity is touched after the last marker */
    void InsertEntity(EntityRef e) { elements.back().InsertEntity(e->index); }
    /** Insert a side distance, which happened after the marker indicated and
     * applies all the way to the current bottom. */
    void Insert(unsigned e1, int e2, double d, iterator from) { Insert(e1, e2, d, from, elements.end()); }
    /** Insert a side distance, which happened all along between two markers indicated. */
    void Insert(unsigned e1, int e2, double d, iterator from, iterator to);
    /** Insert a new vertical position: a marker. */
    iterator AddMarker(const std::string &m) { elements.emplace_back(m); return --elements.end(); }
    /** Indicates that a marker is in fact inside another element. */
    void AddMarkerTranslation(const std::string &m, MscElement*e) { marker_translations.emplace(m, e); }
    /** Insert a new vertical position: the top of an element. */
    iterator AddElementTop(const MscElement *e) { elements.emplace_back(e, true); return --elements.end();}
    /** Insert a new vertical position: the bottom of an element. */
    iterator AddElementBottom(const MscElement *e) { elements.emplace_back(e, false); return --elements.end();}
    /** Search for the vertical position of a marker by name. */
    iterator GetIterator(const string &m, bool t);
    /** Search for the vertical position of a marker by name. */
    const_iterator GetIterator(const string &m, bool t) const;
    /** Search for the vertical position of an element by the element pointer. */
    iterator GetIterator(const MscElement *elm, bool t) { return std::find_if(elements.begin(), elements.end(), [&](const DistanceRequirementsSection&e) {return e.marker==MARKER_HERE_STR && e.element==elm && e.top==t; }); }
    /** Search for the vertical position of an element by the element pointer. */
    const_iterator GetIterator(const MscElement *elm, bool t) const { return std::find_if(elements.begin(), elements.end(), [&](const DistanceRequirementsSection&e) {return e.marker==MARKER_HERE_STR && e.element == elm && e.top==t; }); }
    /** Get the end() */
    iterator GetIteratorEnd() { return elements.end(); }
    /** Get the end() */
    const_iterator GetIteratorEnd() const { return elements.end(); }
    /** Get the last currently existing vertical element */
    iterator GetIteratorLast() { return --elements.end(); }
    /** Get the last currently existing vertical element */
    const_iterator GetIteratorLast() const { return --elements.end(); }
    DistanceRequirementsSection Get(const_iterator m1, const_iterator m2) const;
    DistanceRequirementsSection GetAll() const { return Get(elements.begin(), elements.end()); }
    /** Queries from the last section */
    double Query(unsigned e1, int e2) const { return elements.back().Query(e1, e2); }
    /** Queries the largest distance between the marker indicated and
    * applies all the way to the current bottom. */
    double Query(unsigned e1, int e2, const_iterator from) const { return Query(e1, e2, from, elements.end()); }
    /** Query the largest distance, which happened all along between two markers indicated. */
    double Query(unsigned e1, int e2, const_iterator from, const_iterator to) const;
    /** Queries from the entire collection */
    double QueryAll(unsigned e1, int e2) const { return Query(e1, e2, elements.begin(), elements.end()); }
    void CombineAllLeftRightToPair_Add(double gap);
    /** Merges the last hspace requirements to the last element. */
    void MergeAllToHSpace() { _ASSERT(elements.size()==1);  map_manip::MergeMax(hspace_pairs, elements.back().pairs); elements.clear(); }
};
/////////////////////////////////////////////////////////////////////

/** Holds information about one page break */
struct MscPageBreakData : public PageBreakData {
    const EntityCommand *autoHeading;  ///<A non-owning pointer to the automatically inserted heading command. nullptr if none.
    MscPageBreakData(double _y, double _y_span, bool m, EntityCommand *ce=nullptr, double h=0) :
        PageBreakData(XY(0,_y), XY(0, _y_span)), autoHeading(ce)
    { manual = m; headingLeftingSize.x = 0; headingLeftingSize.y = (h ? h : ce ? ce->GetFormalHeight() : 0);}
};

/**Helper struct to manage multiple list of arcs, sorted by
 * a criteria so that always the smallest shall be taken an element from.*/
struct LayoutColumn
{
    /**If this list is put in as a nested parallel block,
     * this is the parent list*/
    LayoutColumn * const parent;
    /**If we have nested parallel blocks under processing
     * this is how many are not yet done.*/
    unsigned number_of_children;
    /** This is the actual list of arcs we contain.*/
    ArcList * const list;
    /** This points to the next arc to process in 'list'.
     * Equals to list->end() if we are done.*/
    ArcList::iterator arc;
    /** Tie-breaker: in case of everything equal, use the column number
     * This tells which column we were in our parallel block series.*/
    const unsigned column;
    /** This is where the next (uncompressed) arc should be placed,
     * the lowest part of any prior element in this column.
     * This is different from y_bottom_all due to elements
     * marked by 'overlap' and 'parallel'. Those increase y_bottom_all,
     * but keep y the same.*/
    double y;
    /**we will never shift elements higher than this runnning value
     * (due to compress).
     * (any element marked with "parallel" will set this to its top)*/
    double y_upper_limit;
    /** True if the previous element was marked with parallel */
    bool previous_was_parallel;
    /** The x extent of the previous element, if it was parallel - else unset */
    Range previous_x_extent;
    /**the lowest element's bottom (largest num value) we have seen in 'list'
     * so far. The lowest of these will be returned from LayoutParallelArcLists().
     * This is not always that of the last element so far, if the last
     * element has been shifted up due to compress and its bottom is no longer
     * the lowest.*/
    double y_bottom_all;
    /**These contain all arc_covers for elements in 'list'.
     * It does not contain the covers of elements of the parent.*/
    AreaList covers;
    /** These contain all arc_covers from completed children
     * As long as not all children are completed.*/
    AreaList completed_children_covers;
    unsigned last_action;
    LayoutColumn(ArcList *a, unsigned c, double Y = 0, double upper=0, bool par=false,
                 Range xext = Range(), LayoutColumn *p = nullptr, unsigned action=0) :
        parent(p), number_of_children(0), list(a), arc(list->begin()), column(c), y(Y),
        y_upper_limit(upper), previous_was_parallel(par), previous_x_extent(xext),
        y_bottom_all(Y), last_action(action) {}
    /** Return true if a particular column is my parent, which means that I was
     * defined in it. So in case of {a; b; {c; d;} e;} the outer column is a parent of the inner.*/
    bool IsMyParent(const LayoutColumn *col) const
        { const LayoutColumn *cand = this;  while (cand && col!=cand) cand = cand->parent; return nullptr != cand; }
};

/** Data we store about a named arc, that can be referenced via @\r(<name>) */
struct MscRefNameData : public RefNameData
{
    ArcBase* arc = nullptr;         ///<The arc or nullptr if not shown (set in FinalizeLabels())
};

class MscProgress : public Progress<EBulkSection, EBulkSection::MAX_BULK_SECTION,
                                     EArcSection, EArcSection::MAX_ARC_SECTION,
                                     EArcCategory, EArcCategory::MAX_CATEGORY>
{
public:
    explicit MscProgress(ProgressCallback cb = nullptr, void *d=nullptr, double g=1)
        : Progress({
            {EBulkSection::PARSE, "Parsing"},
            {EArcSection::POST_PARSE, "Post Parse Process"},
            {EArcSection::FINALIZE_LABELS, "Finalizing Labels"},
            {EArcSection::WIDTH, "Calculating Widths"},
            {EArcSection::LAYOUT, "Layouting"},
            {EBulkSection::AUTOPAGINATE, "Autopaginating"},
            {EArcSection::PLACEWITHMARKERS, "Laying out Verticals"},
            {EArcSection::NOTES, "Laying out Notes"},
            {EArcSection::POST_POS, "Post Position Processing"},
            {EArcSection::DRAW, "Drawing"},
        }, ProgressBase::EPreferredAbortMethod::EXCEPTION, cb, d, g) {}
};

/** The main class holding a signalling chart
  (This is a non movable, non copyable object, once it has arcs in it, due to the
  cross-references between them.)

  # Coordinate Spaces

  - The pixel coordinate space (1 pixel = 1/72 inch for vector output) is the most natural.
    Font heights are represented in pixels. This is also called "chart coordinate space"
    when drawing, see the comment for Canvas.
  - The 'pos' coordinate space, which is only used on the horizontal axis to place entities.
    The 'pos' attribute of entities uses this. For historical purposes 1 unit of the 'pos'
    coordinate space is 130 pixels. The conversion is done by Msc::XCoord().

  # Compress mechanism

    The compress mechanism (invoked by setting compress attribute to yes)
    changes the layout of Arcs.
    As normally they are placed in LayoutArcList() below the arc above.
    However, they can be placed higher if
    - its upper mainline is still below the lower mainline of the object above
    - it does not overlap in any way with the object(s) above.

    The object is never placed lower than the bottom of the arc above.
*/
class MscChart : public ChartBase<MscContext, MscProgress, MscRefNameData, MscPageBreakData> {
public:
    /** @name Language attributes
    * @{ */
    std::string GetLanguageDescription() const override { return "Signalling Chart"; } ///<Returns the human-readable (UTF-8) short description
    std::vector<std::string> GetLanguageExtensions() const override { return {"signalling", "msc"}; } ///<Returns a set of extensions in order of preference
    std::string GetLanguageEntityName() const override { return "entity"; }
    std::string GetLanguageDefaultText() const override; ///<Returns the default text of the chart in UTF-8
    bool GetLanguageHasAutoheading() const override { return true; } ///<True if the chart has automatic headings
    bool GetLanguageHasElementControls() const override { return true; } ///<True if the elements of the chart support GUI controls
    std::unique_ptr<Csh> CshFactory(Csh::FileListProc proc, const LanguageCollection* languages) const override;
    std::map<std::string, std::string> RegisterLibraries() const override;
    /** @} */


    /** Data we store about a marker */
    struct MarkerData {
        FileLineCol line; ///<Marker's location in the input file, invalid if a built-in marker
        double      y;    ///<Marker's vertical position in chart space
    };

    /** @name Members holding chart content (entities, arcs, definitions, etc.)
     * @{ */
    EntityList                    AllEntities;     ///<A list of all entities defined (in no particular order)
    NEntityList                   ActiveEntities;  ///<A list of the entities that are not hidden as part of a collapsed group entity
    EntityRef                     StartEntity;     ///<A virtual entity representing the very left side of the chart.
    EntityRef                     LNote;           ///<A virtual entity representing the line separating the left side comments from the chart body.
    EntityRef                     LSide;           ///<A virtual entity representing the left side of the chart body (some margin away from LNote)
    EntityRef                     RSide;           ///<A virtual entity representing the right side of the chart body (some margin away from RNote)
    EntityRef                     RNote;           ///<A virtual entity representing the line separating the right side comments from the chart body.
    EntityRef                     EndEntity;       ///<A virtual entity representing the very right side of the chart.
    EntityAppList                 AutoGenEntities; ///<A list of entity appearance objects, one for each implicitly defined entity. After parsing, they are appended to the first EntityCommand.
    ArcList                       Arcs;            ///<The list of all arcs in the chart (in order of definition). Notes are moved away from here in PostParseProcess().
    std::map<string, MarkerData, std::less<>> Markers; ///<A set of named markers. Each one was defined via a 'mark' command.
    std::map<double, FillAttr>    Background;      ///<A map of background fill indexed by the position downward of which this fill is applicable. Each entry was created by a background.* chart option.
    Contour                       HideELinesHere;  ///<A complex contour used as a mask when drawing entity lines.
    ArcList                       EndNotes;        ///<We move all endnotes here during PostParseProcessArcList(). We re-append them in PostParseProcess().
    CommandNoteList               Notes;           ///<All notes are moved here after PostParseProcess
    NPtrList<const MscElement>    NoteBlockers;    ///<Ptr to all elements that may block a floating note and which therefore should not overlap with them (we do not own them here)
    /** Maps elements to all the 'by ref' symbols and verticals that reference them.
     * Filled in during Width calculation (when all elements that disappear are already
     * removed) and used in Layout. If one element is referred to by multiple 
     * symbols/verticals, those are stored in order of their appearance in the source files, 
     * since unhinted 'insert' and 'emplace' always inserts at the upper bound. */
    std::multimap<ArcBase*, ArcBase*> ByRefs;
    /** @} */

protected:
    /** @name Members holding calculated information on chart geometry
     * @{ */
    friend class EntityCommand;
    Block  drawing;              ///<The area where chart elements can be (total minus the comment lanes at the side)
    double headingSize;          ///<Y size of first heading row (collected during PostPosProcess())
    double width_attr;           ///<The value of any width attribute the user specified (-1 if none)
    /** @} */

public:
    /** Returns the area where chart elements can be (total minus the comment lanes at the side)*/
    const Block &GetDrawing() const {return drawing;}
    /** Returns the distance of the bottom of the top entity headings from chart top. (for AutoSplit)
     * If there are notes above the heading, they are included.*/
    double GetHeadingSize() const {return headingSize-total.y.from;}
    /** Returns the width attribute specified by the user. Can also be from a hscale in mscgen mode.*/
    double GetWidthAttr() const override { return width_attr; }
    /** Returns the default distance for special endings in pixels.
     * This is 30 multiplied by hscale or 30 for hscale==auto.
     * We call it many times during parsing when hscale may still change,
     * which may lead to an ending having a bad size.
     * But since hscale is usually set at the top of the file, this is
     * seldom visible...*/
    double GetDefaultSpecialEndingSize() const { return XCoord(30./130.); }

    /** @name Members defining various spacing values.
     * These should have an integer value for nice drawing on bitmaps.
     * @{ */

    double chartTailGap;     ///<Gap at the bottom of the page for lengthening entity lines
    double selfArrowYSize;   ///<Self Pointing arc Y size.
    double headingVGapAbove; ///<Vertical gap above headings
    double headingVGapBelow; ///<Vertical gap below headings
    double boxVGapOutside;   ///<Vertical gap above and below boxes
    double boxVGapInside;    ///<Vertical gap inside boxes
    double arcVGapAbove;     ///<Vertical gap above and below arcs
    double arcVGapBelow;     ///<Vertical gap above and below arcs
    double discoVgap;        ///<How much extra space above and below a discontinuity line (...)
    double titleVgap;        ///<How much extra space above and below a title
    double subtitleVgap;     ///<How much extra space above and below a subtitle
    double nudgeSize;        ///<Size of the `nudge` command
    double activeEntitySize; ///<The width of entity activation bars
    double compressGap;      ///<Size of gap we keep between elements at compress. We expand by half of it
    double hscaleAutoXGap;   ///<Size of horizontal gap between elements at hscale=auto
    double sideNoteGap;      ///<Gap between the side comment line and the comments
    double defWNoteWidth;    ///<The default width for word wrapping notes in pos "space"
    double entityShapeHeight;///<The default height of an entity shape with size=normal.
    /** @} */

    /** @name Parse Options
     * @{ */
    bool pedantic;                   ///<If we require pre-defined entities.
    bool simple_arc_parallel_layout; ///<If false, we use MscChart::HeightArcLists() to lay out ParallelBlocks*/
    EMscgenCompat mscgen_compat;     ///<How shall we approach mscgen compatibility.

    EntityCollapseCatalog force_entity_collapse;      ///<These entities must be collapsed/expanded
    BoxSignatureCatalog   force_box_collapse;         ///<These boxes must be collapsed/expanded, also contains an update after parsing
    BoxSignatureCatalog   force_box_collapse_instead; ///<During parsing, this holds an updated version of the box collapse/expanded instructions (removing nonexistent boxes, etc)
    InlineSignatureCatalog inline_gui_state;          ///<The GUI state of inlined charts
    InlineSignatureCatalog inline_gui_state_instead;  ///<During parsing, this holds an updated version of the GUI state of inlined charts
    /** @} */

    MscChart(FileReadProcedure *p, void *param, const LanguageCollection* languages);
    ~MscChart() override;
    static std::unique_ptr<Chart> Factory(FileReadProcedure *p, void *param, const LanguageCollection* languages)
        { return std::make_unique<MscChart>(p, param, languages); }
    bool AddCommandLineArg(const std::string& arg) override;
    void AddCommandLineOption(const Attribute &a) override;
    bool GetPedantic() const noexcept override { return pedantic; }
    void SetPedantic(bool pedantic) noexcept override { this->pedantic = pedantic; Chart::SetPedantic(pedantic); }

    bool ApplyForcedDesign(const string& name) override;
    bool DeserializeGUIState(std::string_view) override;
    std::string SerializeGUIState() const override;
    bool ControlClicked(Element *, EGUIControlType) override;
    int SetDesign(bool full, const string &design, bool force, ArcBase **ret,
                  const FileLineColRange &l = FileLineColRange(FileLineCol(0,0,0), FileLineCol(0,0,0)));
    void SwitchToMscgenCompatMode();

    EntityCommand *CEForComments(const MscStyle &s, const FileLineColRange &l);
    ArcBase *AddAttribute(const Attribute&);
    bool AddDesignAttribute(const Attribute&);
    static void AttributeNames(Csh &csh, bool designOnly);
    static bool AttributeValues(std::string_view attr, Csh &csh);

    MarkerData* GetMarker(std::string_view name);

    /** Return the entity with the smaller (min=true) or larger (min=false) `pos` value*/
    EntityRef EntityMinMaxByPos(EntityRef i, EntityRef j, bool min) const;
    /** Return the entity with the smaller (min=true) or larger (min=false) `pos` value*/
    CEntityRef EntityMinMaxByPos(CEntityRef i, CEntityRef j, bool min) const;
    /** Return the entity with the smaller `pos` value*/
    EntityRef EntityMinByPos(EntityRef i, EntityRef j) const { return EntityMinMaxByPos(i, j, true); }
    /** Return the entity with the smaller `pos` value*/
    CEntityRef EntityMinByPos(CEntityRef i,CEntityRef j) const { return EntityMinMaxByPos(i, j, true); }
    /** Return the entity with the larger `pos` value*/
    EntityRef EntityMaxByPos(EntityRef i, EntityRef j) const { return EntityMinMaxByPos(i, j, false); }
    /** Return the entity with the larger `pos` value*/
    CEntityRef EntityMaxByPos(CEntityRef i, CEntityRef j) const { return EntityMinMaxByPos(i, j, false); }
    /** Finds entity named `e` (mentioned at position `l` in the source file).
     * If not found, it creates one (and uses 'l' only then). */
    EntityRef FindAllocEntity(std::string_view e, const FileLineColRange &l);
    EntityRef FindLeftRightDescendant(EntityRef, bool left, bool stop_at_collapsed);
    EntityRef FindActiveParentEntity(EntityRef);
    EntityRef FindWhoIsShowingInsteadOf(EntityRef, bool left);
    string ListGroupedEntityChildren(CEntityRef ei) const;
    bool ErrorIfEntityGrouped(CEntityRef, FileLineCol l);
    bool IsMyParentEntity(const string &child, const string &parent) const;
    bool IsMyParentEntity(CEntityRef child, CEntityRef parent) const;
    double GetEntityMaxPos() const;
    double GetEntityMaxPosExp() const;
    EntityRef GetNeighbour(EntityRef e, bool right, bool all);
    /** True if `e` is one of nullptr, NoEntity, LSide, LNote, RSide, RNote or EndEntity.*/
    bool IsVirtualEntity(const Entity *e) const {return e==nullptr || e==StartEntity || e==LNote || e==LSide || e==RSide || e==RNote || e==EndEntity;}
    /** True if `e` is one of nullptr, NoEntity, LSide, LNote, RSide, RNote or EndEntity.*/
    bool IsVirtualEntity(const Entity &e) const { return IsVirtualEntity(&e); }
    /** True if `e` is one of nullptr, NoEntity, LSide, LNote, RSide, RNote or EndEntity.*/
    bool IsVirtualEntity(EntityList::const_iterator e) const { return IsVirtualEntity(&*e); }
    /** True if `e` is one of nullptr, NoEntity, LSide, LNote, RSide, RNote or EndEntity.*/
    bool IsVirtualEntity(NEntityList::const_iterator e) const { return IsVirtualEntity(*e); }
    /** Moves the contents of `a` to the end of the arc list of the chart*/
    void AddArcs(ArcList *a) {if (!a) return; Arcs.splice(Arcs.end(), *a); delete a;}
    Arrow *CreateArrow(ArrowSegmentData data, std::string_view s, const FileLineColRange &sl,
                       std::string_view d, bool fw, const FileLineColRange &dl, bool had_param);
    /** Create a block arrow from an arrow. Returns null, emits an error if not possible
     * On success, it brings the given arc to a "valid, but unspecified state" .*/
    BlockArrow *CreateBlockArrow(ArcBase *);
    /** Pop the top context. May return arcs to be appended as a result (SetNumbering)*/
    std::unique_ptr<ArcBase> PopContext();

    /** Parse a piece of input text as a new input file.*/
    void ParseText(std::string_view input, const FileLineCol& first_char) override;
    void PostParseProcessArcList(Canvas &canvas, bool hide, ArcList &arcs, bool resetiterators, EntityRef &left,
                                 EntityRef &right, Numbering &number, MscElement **note_target);
    void PostParseProcess(Canvas &canvas);
    /** Call FinalizeLabels() for each element in `arcs*/
    void FinalizeLabelsArcList(ArcList &arcs, Canvas &canvas);

    EDirType GetTouchedEntitiesArcList(const ArcList &, NEntityList &el, EDirType dir=EDirType::INDETERMINATE) const;

    /** Returns the final value of the hscale chart option.
     * We take it from the outermost scope, even if there are smaller scopes
     * still open. This is because if the user opens a "defdesign {" at the very
     * end of the file, without closing it, a totally empty (no hscale) context
     * will be the current at the end of the parsing. */
    double GetHScale() const {_ASSERT(Contexts.size() && Contexts.front().hscale); return *Contexts.front().hscale;}
    /** Converts an x coordinate from pos coordinat space to pixel space. Always returns an integer */
    double XCoord(double pos) const {return floor(pos*130*(GetHScale()>0?GetHScale():1)+0.5);} //rounded
    /** Returns the x coordinate of the middle of an entity in pixel space, rounded*/
    double XCoord(CEntityRef i) const {return XCoord(i->pos);} //rounded

    void WidthArcList(Canvas &canvas, ArcList &arcs, DistanceRequirements &vdist);
    double LayoutArcList(Canvas &canvas, ArcList &arcs, AreaList *cover);
    double LayoutParallelArcLists(Canvas &canvas, std::list<LayoutColumn> &columns, AreaList *cover);
    double PlaceListUnder(Canvas &canvas, ArcList &arcs, double start_y,
                          double top_y, const AreaList &area_top,
                          bool forceCompress=false, AreaList *ret_cover=nullptr);
    void ShiftByArcList(ArcList &arcs, double y);
    void InsertAutoPageBreak(Canvas &canvas, ArcList &arcs, ArcList::iterator i,
                             double pageBreak, bool addHeading);
    void AddPageBreak(double y, bool manual, EntityCommand *autoHeading=nullptr);
    double PageBreakArcList(Canvas &canvas, ArcList &arcs, double netPrevPageSize,
                            double pageBreak, bool &addCommandNewpage, bool addHeading,
                            bool canChangePBPos, bool dontshiftall);
    void CollectPageBreaks();
    void CollectPageBreakArcList(ArcList &arcs);
    void AutoPaginate(Canvas &canvas, double pageSize, bool addHeading);
    void CalculateWidthHeight(Canvas &canvas,
                              bool autoPaginate, bool addHeading, XY pageSize, bool fitWidth);
    void PlaceWithMarkersArcList(Canvas &canvas, ArcList &arcs);
    void PlaceFloatingNotes(Canvas &canvas);

    void InvalidateNotesToThisTarget(const MscElement *target);
    void RemoveFromNotes(const Note *note);

    /** Hides the entity lines in `area` */
    void HideEntityLines(const Contour &area) {HideELinesHere += area;}
    /** Hides the entity lines in `area` */
    void HideEntityLines(const Block &area) {HideELinesHere += Contour(area);}
    void PostPosProcessArcList(Canvas &canvas, ArcList &arcs, Chart *ch);
    void RegisterCoverArcList(const ArcList &arcs, EMscDrawPass pass);

    void RegisterLabelArcList(ArcList &arcs) { for (auto &pArc : arcs) pArc->RegisterLabels(); } ///<Register all labels in an arc list
    void RegisterAllLabels() override { RegisterLabelArcList(Arcs); } ///<Register all labels in Arcs
    void CollectIsMapElementsArcList(ArcList &arcs, Canvas &canvas) { for (auto &pArc : arcs) pArc->CollectIsMapElements(canvas); }  ///<Collect all ISMAPs from an arc list.
    void CollectIsMapElements(Canvas &canvas) override { CollectIsMapElementsArcList(Arcs, canvas); } ///<Collect all ISMAPs from an Arcs
    void CompleteParse(bool autoPaginate=false,
                       bool addHeading=true, XY pageSize=XY(0,0),
                       bool fitWidth=true, bool collectLinkInfo=false) override;

    void DrawEntityLines(Canvas &canvas, double y, double height, NEntityList::const_iterator from, NEntityList::const_iterator to);
    void DrawEntityLinesIncludeLast(Canvas &canvas, double y, double height, CEntityRef from, CEntityRef to_included)
         { DrawEntityLines(canvas, y, height, ActiveEntities.Find_by_Ptr(from), ++ActiveEntities.Find_by_Ptr(to_included)); }
    /** Draw all entity lines between vertical positions `y` and `y+height`*/
    void DrawEntityLines(Canvas &canvas, double y, double height)
         {DrawEntityLines(canvas, y, height, ActiveEntities.cbegin(), ActiveEntities.cend());}

    void DrawArcList(Canvas &canvas, const ArcList &arcs, Range yDrawing, EMscDrawPass pass);
    void DrawChart(Canvas &canvas, Range yDrawing, bool pageBreaks);

    void DrawHeaderFooter(Canvas &canvas, unsigned page) override;
    void Draw(Canvas& canvas, bool pageBreaks, unsigned page) override;
    void DrawComplete(Canvas &canvas, bool pageBreaks, unsigned page) override;
    void SetToEmpty() override;
};

} //namespace

using msc::MscChart;

#endif

/* END OF FILE */
