/*
    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 boxes.h The declaration of classes for boxes, pipes, divider and parallel constructs.
 * @ingroup libmscgen_files */


#if !defined(BOXES_H)
#define BOXES_H

#include "arcs.h"

namespace msc {

/** The collapse/expand status of a box.
 * Keep these values for backwards compatibility at serialization file version before 7. */
enum class EBoxCollapseType {
    INVALID=0,   ///<Invalid value
    EXPAND=1,    ///<The box is expanded.
    COLLAPSE=2,  ///<The box is collapsed to a box with no content
    BLOCKARROW=3 ///<The box is collapsed to a block arrow
};

char EBoxCollapseTypeToChar(EBoxCollapseType t);
EBoxCollapseType CharToEBoxCollapseType(char c);

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

/** Box attributes defining a box.
 * We save these to determine, if a box previously collapsed on the GUI
 * is the same as a box after re-compilation (and potentially source text modification)*/
struct BoxSignature {
    FileLineColRange file_pos;  ///<The location of the box in the input file.
    std::string label;          ///<The label of the box (what the user specified)
    std::string src;            ///<The src of the box (what the user specified)
    std::string dst;            ///<The src of the box (what the user specified)
    unsigned content_len;       ///<How many elements are in 'content'
    std::strong_ordering operator <=> (const BoxSignature& o) const noexcept = default;
    BoxSignatureCatalog::iterator WhichIsSimilar(BoxSignatureCatalog &cat) const;
    bool Deserialize(std::string_view &s);
    /** Serialize the signature to a string*/
    std::string Serialize() const;
};



/** One box (potentially in a box series) with or without elements in it */
class Box : public LabelledArc
{
    friend class Pipe;
    friend class BoxSeries;
    friend struct BoxSignature;
protected:
    EArcSymbol       type;            ///<What kind of symbol was used when defining this box.
    EntityRef        src;             ///<The left entity specified by the user (or calculated from content)
    EntityRef        dst;             ///<The right entity specified by the user (or calculated from content)
    string           tag_label;       ///<The tag label as the user specified it. Empty if none.
    Label            parsed_tag_label;///<The tag label processed and parsed into lines. Set in PostParseProcess()
    EBoxCollapseType collapsed;       ///<The collapseStatus of the box.
    bool             drawEntityLines; ///<True if we draw the entity lines. Only for boxes with content: empty boxes never draw lines
    ArcList          content;         ///<The arcs contained within the box.
    BoxSignature     sig;             ///<Our signature (filled in Constructor and AddAttributeList)

    mutable double height_w_lower_line; ///<Height of this box including its lower line
    mutable double sx_text;             ///<Left boundary of label
    mutable double dx_text;             ///<Right boundary of label
    mutable double y_text;              ///<Top boundary of label
    mutable Contour text_cover;         ///<Area covered by the label
    mutable double sx_tag;              ///<Left boundary of tag label (if any)
    mutable double dx_tag;              ///<Right boundary of tag label (if any)
    mutable double y_tag;               ///<Top boundary of tag label (if any)
    mutable Block tag_outer_edge;       ///<The outer edge of the tag rectangle (must be clipped by the inner edge of the box)
    mutable Contour tag_cover;          ///<Area covered by the tag (not just the label of it) - you can use it to clip
public:
    /** Enumerating mscgen-style boxes. */
    const enum Emscgen_compat
    {
        MSCGEN_COMPAT_NONE, MSCGEN_COMPAT_BOX, MSCGEN_COMPAT_ABOX, MSCGEN_COMPAT_RBOX, MSCGEN_COMPAT_NOTE
    } mscgen_compat; ///<Set to other than MSCGEN_COMPAT_NONE, if this box was defined as an mscgen box, abox, rbox or note.
    Box(EArcSymbol t, const FileLineColRange &l, std::string_view s, const FileLineColRange &sl,
        std::string_view d, const FileLineColRange &dl, MscChart *msc);
    Box(Emscgen_compat c, const FileLineColRange &l, std::string_view s, const FileLineColRange &sl,
        std::string_view d, const FileLineColRange &dl, MscChart *msc);
    bool CanBeNoted() const override { return true; }
    const BoxSignature* GetSignature() const override { return &sig; }
    const MscStyleCoW *GetRefinementStyle4BoxSymbol(EArcSymbol t) const;
    void SetStyleBeforeAttributes(AttributeList*) override {} //we do it in AddAttributeList() below
    Box* AddArcList(gsl::owner<ArcList*>l);
    void AddAttributeList(gsl::owner<AttributeList*>) override;
    bool AddAttribute(const Attribute &) override;
    static void AttributeNames(Csh &csh);
    static bool AttributeValues(std::string_view attr, Csh &csh);
    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
        Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;
    void FinalizeLabels(Canvas &canvas) override;
    void Layout(Canvas &/*canvas*/, AreaList * /*cover*/) override { _ASSERT(0); }
    void ShiftBy(double y) override;
    void RegisterCover(EMscDrawPass /*pass*/) const override { _ASSERT(0); } //will never be called
    void Draw(Canvas &/*canvas*/, EMscDrawPass /*pass*/) const override { _ASSERT(0); } //will never be called
};

/** A box series - used even for a single box.*/
class BoxSeries : public ArcBase
{
protected:
    UPtrList<Box> series;         ///<The boxes in the series - may contain only a single one
    int           drawing_variant;///<One of 3 variants on how to draw double or triple lines
    AttributeList collected_attrs;///<The collected attributes of all boxes in the series - applied to each new box.
    StoredNotes tmp_stored_notes; ///<A set of notes attached to us.

    mutable double total_height;  ///<Total height of the series
    mutable double left_space;    ///<How much do we expand left of the entity line of src. Includes linewidth.
    mutable double right_space;   ///<How much do we expand right of the entity line of dst. Includes linewidth.
public:
    /** Create a series using a box as a first element (and taking ownership!)*/
    BoxSeries(gsl::owner<Box*> first, gsl::owner<AttributeList*> l);
    bool CanBeAlignedTo() const override { return true; }
    bool HasCenterline() const override { return true; }
    bool StoreNoteTemporarily(Note* cn) override;
    void ReinsertTmpStoredNotes(ArcList& list, ArcList::iterator after) override;
    /** Expands the file_pos of the first element in a series (used when adding the 'box' keyword)*/
    void ExpandFirstLineEnd(const FileLineColRange &l) { _ASSERT(series.size()); if (series.size()) series.front()->ExpandLineEnd(l); }
    /** Add the first and append subsequent boxes to the series */
    BoxSeries* AddBox(gsl::owner<Box*> f, gsl::owner<AttributeList*> l);
    EDirType GetToucedEntities(NEntityList &el) const override;

    bool CanJoin() const override { return true; }
    bool RemoveIndicator() const override { return false; }
    EndConnection GetConnection(bool is_src) const override;
    ArrowEnding GetSidePos(bool is_src) const override;
    bool SetEndingDueToJoin(bool /*is_src*/, const ArrowEnding&, bool /*test_only*/)  override { _ASSERT(0); return false; } //Never unspecified
    void SetCurlyJoin(bool /*is_src*/, ArcBase*, bool /*src_of_prev*/) override { _ASSERT(0); } //never curly
    double GetCenterline(CenterlineSelector /*where*/) const override { return yPos + total_height / 2; }
    double GetActSize(bool /*at_src*/) const override { _ASSERT(0); return 0; } //This never does curly joins

    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
        Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;
    void FinalizeLabels(Canvas &canvas) override;
    void Width(Canvas &canvas, DistanceRequirements &vdist) override;
    void Layout(Canvas &canvas, AreaList *cover) override;
    void ReLayout(double total_height, Canvas &canvas, AreaList *cover);

    Range GetVisualYExtent(bool include_comments) const override;

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

/** One pipe segment */
class Pipe : public LabelledArc
{
    friend struct pipe_compare;
    friend class PipeSeries;
protected:
    EntityRef src;             ///<The left entity specified by the user (or calculated)
    EntityRef dst;             ///<The right entity specified by the user (or calculated)
    bool      drawEntityLines; ///<True if we draw the entity lines. Only for pipes with content: empty boxes never draw lines

    mutable bool pipe_connect_back; ///<True if this segment connects to a previous segment on its left side
    mutable bool pipe_connect_forw; ///<True if this segment connects to a subsequent segment on its right side
    mutable double left_space;      ///<How much we expand beyond src. Includes linewidth.
    mutable double right_space;     ///<How much we expand beyond dst. Includes linewidth and shadow.
    mutable double sx_text;         ///<Left boundary of label
    mutable double dx_text;         ///<Right boundary of label
    mutable double y_text;          ///<Top boundary of label
    mutable Contour text_cover;     ///<Area covered by the label
    mutable Block pipe_block;       ///<A representative rectangle of the pipe
    mutable Contour pipe_shadow;    ///<The outer contour of the pipe used to draw the shadow
    mutable Contour pipe_body_line; ///<The line around the body of the pipe (excluding the hole at the end)
    mutable Contour pipe_whole_line;///<The line around the whole of the pipe (including the hole at the end)
    mutable Contour pipe_body_fill; ///<The fill area of the body of the pipe (excluding the hole at the end)
    mutable Contour pipe_hole_fill; ///<The fill area of the hole at the end
    mutable Contour pipe_hole_line; ///<The line around the hole at the end
    mutable Contour pipe_hole_curve;///<The curve at the hole end
public:
    const EArcSymbol type; ///<What box symbol was used to define this pipe.
    /** Upgrade a box to a pipe: this is how we create pipes normally. Deletes 'box'*/
    Pipe(gsl::owner<Box*> box);
    bool CanBeNoted() const override { return true; }
    const MscStyleCoW *GetRefinementStyle4PipeSymbol(EArcSymbol t) const;
    void SetStyleBeforeAttributes(AttributeList *l) override;
    bool AddAttribute(const Attribute &) override;
    static void AttributeNames(Csh &csh);
    static bool AttributeValues(std::string_view attr, Csh &csh);
    void Layout(Canvas &/*canvas*/, AreaList * /*cover*/) override { _ASSERT(0); }
    void ShiftBy(double y) override;
    void RegisterCover(EMscDrawPass /*pass*/) const override { _ASSERT(0); } //will never be called
    /** Helper to draw various parts of the pipe */
    void DrawPipe(Canvas &canvas, EMscDrawPass pass, bool topSideFill, bool topSideLine,
                  bool backSide, bool text, double next_lw, int drawing_variant);
    void Draw(Canvas &/*canvas*/, EMscDrawPass /*pass*/) const override { _ASSERT(0); } //will never be called
};

/** A pipe series - used even for a pipe with one segment */
class PipeSeries : public ArcBase
{
protected:
    UPtrList<Pipe> series;         ///<The pipes in the series
    ArcList        content;        ///<Any arcs contained in the pipe series
    int            drawing_variant;///<One of 3 variants on how to draw double or triple lines

    mutable double total_height;   ///<Total height of the pipe series
public:
    /** Create a series using a pipe as a first element */
    PipeSeries(gsl::owner<Pipe*> first);
    /** Add another pipe segment (can be left or right from existing ones) */
    PipeSeries* AddFollowWithAttributes(gsl::owner<Pipe*> f, gsl::owner<AttributeList*> l);
    /** Add actual content */
    PipeSeries* AddArcList(gsl::owner<ArcList*> l);
    bool CanBeAlignedTo() const override { return true; }
    bool HasCenterline() const override { return true; }

    EDirType GetToucedEntities(NEntityList &el) const override;
    double GetCenterline(CenterlineSelector /*where*/) const override { return yPos + total_height / 2; }
    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
        Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;
    void FinalizeLabels(Canvas &canvas) override;
    void Width(Canvas &canvas, DistanceRequirements &vdist) override;
    void CalculateContours(Area *pipe_body_cover = nullptr);
    void Layout(Canvas &canvas, AreaList *cover) override;
    Range GetVisualYExtent(bool include_comments) const override;
    void ShiftBy(double y) override;
    void CollectPageBreak() override;
    double SplitByPageBreak(Canvas &canvas, double netPrevPageSize,
        double pageBreak, bool &addCommandNewpage,
        bool addHeading, ArcList &res) override;
    void PlaceWithMarkers(Canvas &canvas) override;
    virtual void PostPosProcess(Canvas &, Chart *ch) override;
    void RegisterLabels() override;
    void CollectIsMapElements(Canvas &canvas) override;
    void RegisterCover(EMscDrawPass pass) const override;
    void Draw(Canvas &canvas, EMscDrawPass pass) const override;
};


/** A divider, gap, title, subtitle or nudge command */
class Divider : public LabelledArc
{
protected:
    const EArcSymbol type;     ///<What kind of divider symbol was used to define us
    const bool nudge;          ///<True if created from a `nudge` command
    const bool title;          ///<True if created from a `title` or `subtitle` command
    bool wide;                 ///<If true, we keep no margin and add no gap above & below (for copyright text)
    const double extra_space;  ///<Extra vertical space above and below: depends on how we created this object

    mutable double centerline; ///<Y coordinate of the centerline relative to yPos
    mutable double text_margin;///<left and right text margin from chart edge
    mutable double line_margin;///<left and right line margin from chart edge
    mutable Contour text_cover;///<The cover of the label
public:
    Divider(EArcSymbol t, MscChart *msc);
    bool CanBeAlignedTo() const override { return !nudge; } //nudges cannot be targets of a vertical
    bool HasCenterline() const override { return !nudge; } //nedges have no centerline
    bool BeforeAutoGenEntities() const override { return title; }
    void SetStyleBeforeAttributes(AttributeList *l) override;
    bool AddAttribute(const Attribute &) override;
    static void AttributeNames(Csh &csh, bool nudge, bool title);
    static bool AttributeValues(std::string_view attr, Csh &csh, bool nudge, bool title);
    double GetCenterline(CenterlineSelector /*where*/) const override { return yPos + centerline; }
    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                              Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;
    void Width(Canvas &canvas, DistanceRequirements &vdist) override;
    void Layout(Canvas &canvas, AreaList *cover) override;

    void ShiftBy(double y) override;
    virtual void PostPosProcess(Canvas &, Chart *ch) override;
    void RegisterLabels() override;
    void CollectIsMapElements(Canvas &canvas) override;
    void Draw(Canvas &canvas, EMscDrawPass pass) const override;
};

/** Multiple parallel blocks */
class ParallelBlocks : public ArcBase
{
public:
    /** Describes the three layout algorithm for parallel blocks.*/
    enum EParallelLayoutType
    {
        INVALID_LAYOUT = 0,
        OVERLAP,          ///<Lay out each block independently and allow overlaps
        ONE_BY_ONE,       ///<Lay out elements from each block one by one and avoid overlap
        ONE_BY_ONE_MERGE, ///<Lay out elements from each block one by one and avoid overlap, merge with parallel block around us
    };
    /** Specify vertical alignment. */
    enum EVerticalIdent
    {
        NOT_SET = 0,
        TOP,    ///<Align on top
        MIDDLE, ///<Align by middle
        BOTTOM, ///<Align at bottom
    };
protected:
    /** Represents one parallel block */
    struct Column
    {
        ArcList arcs;          ///<The list of arcs in this column
        EVerticalIdent ident;  ///<For OVERLAP layout, how to ident this block.
        FileLineCol ident_pos; ///<Location of the vertical_ident attribute
        double height;         ///<column height
        /** Create a column from a list or arcs. 'i' tells us how to vertically align this column.*/
        Column(ArcList &&a, EVerticalIdent i) : arcs(std::move(a)), ident(i), height(0) { ident_pos.MakeInvalid(); }
        /** True if this column (parallel block) has no arcs included. */
        bool IsEmpty() const { return arcs.size()==0; }
    };
    friend class MscChart; //to manage our layout in LayoutParallelArcLists()
                           /** Set yPos, this is where the top of us is.*/
    void SetYPos(double y) { yPos = y; }
    /** Set our height so that y is our bottom. */
    void SetBottom(double y) { height = y-yPos; }
    std::list<Column> blocks;       ///<Arc lists, one for each block
public:
    const bool before_autogen_entities;///<True if this is a single-block series defined e.g., to return chart options. Auto headings jump over these.
    const bool unroll;                 ///<True if this is a single-block series defined internally and PostParseProcessArcList() shall unroll these.
    EParallelLayoutType layout;        ///<The layout method
    /** Create a parallel construct from its first block */
    ParallelBlocks(MscChart *msc, ArcList*l, AttributeList *al, bool before, bool u);
    bool CanBeAlignedTo() const override { return true; }
    bool BeforeAutoGenEntities() const override { return before_autogen_entities; }
    /** Add one more parallel block */
    ParallelBlocks* AddArcList(ArcList*l, AttributeList *al);
    bool AddAttribute(const Attribute &) override;
    static void AttributeNames(Csh &csh, bool first);
    static bool AttributeValues(std::string_view attr, Csh &csh, bool first);
    EDirType GetToucedEntities(NEntityList &el) const override;
    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
        Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;
    void FinalizeLabels(Canvas &canvas) override;
    void Width(Canvas &canvas, DistanceRequirements &vdist) override;
    void Layout(Canvas &canvas, AreaList *cover) override;
    Range GetVisualYExtent(bool include_comments) const override;

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

/** Inline series or arcs. Used internally. */
class JoinSeries : public ArcBase
{
protected:
    ArcList content;                        ///<The list of arcs contained in us.
    std::list<std::pair<bool, bool>> is_src;///<Empty for a curly series, for non-curly, we store which side we connect. Has one less element than 'content'. After Width()
    JoinSeries(MscChart *msc, ArcList &&c, std::list<std::pair<bool, bool>> &&s) : ArcBase(EArcCategory::PARALLEL, msc), content(std::move(c)), is_src(s) {} ///<Protected constructor used in JoinSeries::Width()
public:
    JoinSeries(MscChart *msc, ArcBase *first);
    bool CanBeAlignedTo() const override { return true; }
    void Append(ArcBase *);
    void ReinsertTmpStoredNotes(ArcList& list, ArcList::iterator after) override;
    EDirType GetToucedEntities(NEntityList &el) const override;
    static JoinableResult CanJoinTo(ArcBase *a, ArcBase *b, ESide side, MscError *Error = nullptr);

    ArcBase* PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
        Numbering &number, MscElement **note_target, ArcBase *vertical_target) override;
    void FinalizeLabels(Canvas &canvas) override;
    void Width(Canvas &canvas, DistanceRequirements &vdist) override;
    void Layout(Canvas &canvas, AreaList *cover) override;
    void FinalizeLayout(Canvas &canvas, AreaList *cover) override;
    Range GetVisualYExtent(bool include_comments) const override;

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

} //namespace

#endif //BOXES_H