/*
    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 block_arrows.h The declaration for Arrows of Block Diagrams.
* @ingroup libblock_files */

#ifndef BLOCK_ARROWS_H
#define BLOCK_ARROWS_H

#include <algorithm>
#include <variant>
#include <map>
#include "block_blocks.h"

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

/** An struct holding the value of a 'via' attribute in the form of a block @ pos.*/
struct ViaAttrSoloBlock
{
    static const char * const route_attr_values[];
    std::string block;     ///<What block was specified
    FileLineCol block_pos; ///<Where the block was in the input file
    enum { LEFT, RIGHT, CENTER, TOP, BOTTOM, TOPLEFT, TOPRIGHT, BOTTOMLEFT, BOTTOMRIGHT } pos; ///<What additional position specified (after the @ symbol) was specified. "CENTER" if none.
};

using ViaAttrCoord = std::array<AlignTo, 2>;
using ViaAttr = std::variant<ViaAttrSoloBlock, ViaAttrCoord>;

inline double Turn(const Edge &e)
{
    return e.IsStraight()||e.IsDot() ? 0 :
        fabs(GetDegree(e.PrevTangentPoint(0), e.GetStart()) - GetDegree(e.GetEnd(), e.NextTangentPoint(1)));
}

inline double Turn(const Edge &e, const Edge &f)
{
    _ASSERT(e.GetEnd()==f.GetStart());
    return fabs(GetDegree(e.PrevTangentPoint(1), e.GetEnd()) - GetDegree(f.GetStart(), f.NextTangentPoint(0)));
}

class ArrowLabel : public BlockInstruction
{
protected:
    /** This constructor is used to create a clone.
     * @param [in] a The arrow label to copy.
     * @param [in] l The location at which we copy.
     * @param [in] prefix The new name prefix to apply to our name.*/
    ArrowLabel(const ArrowLabel &a, const FileLineColRange &l, std::string_view prefix) :
        BlockInstruction(a, l, prefix), percent(a.percent), offset(a.offset),
        is_extension(a.is_extension), has_marker(a.has_marker) {}
    mutable const Path *arrow_path = nullptr;
    mutable const LineAttr *arrow_line = nullptr;
    mutable Path extension_path; ///<If we have a marker outside the path, we create a path here ending at the marker.
public:
    /** @name Position related
     * The position of the label is calculated as follows. Positive values always
     * count towards the end of the arrow, negative values towards its start.
     * If is_extension is true then a positive percent means we count from the end,
     * a negative one means we count from the start, a zero means, we rely on the sign of
     * the offset. If is_extension is false, for positive sign we start from the start
     * and for negative, we start from the end.
     * It is an error if both percent and offset are zero.
     * @{ */
    const double percent;       ///<The position of the label in percentage of the length
    const double offset;        ///<The position of the label as additional pixel offset
    bool is_extension = false;
    /** Returns true if we shall start measuring from the start of the arrow based on 'percent',
     * 'offset' and 'is_extension'. Else we measure from the end.*/
    bool IsFromStart() const { return (percent ? percent<0 : offset<0) == is_extension; }
    /** @} */
    bool has_marker = false;   ///<True if we shall draw a mid-arrowhead at this point
    mutable PathPos pos;       ///<The position of the marker
    mutable XY point;          ///<The position of the marker
    static std::optional<std::pair<double, double>>
        ConvertPosition(BlockChart &chart,
                        std::string_view num1, bool is_perc1, const FileLineColRange &l1,
                        std::string_view num2 = {}, bool is_perc2 = false, const FileLineColRange& l2 = FileLineColRange());
    ArrowLabel(BlockChart &chart, double perc, double offset, const FileLineColRange &l);
    ArrowLabel(const ArrowLabel &) = default;
    ArrowLabel(ArrowLabel &&) = default;
    bool AddAttribute(const Attribute &a) override;
    /** Add the attribute names for block type `t` we take to `csh`.*/
    static void AttributeNames(Csh &);
    /** Add a list of possible attribute value names for block type `t` to `csh` for attribute `attr`.*/
    static bool AttributeValues(std::string_view /*attr*/, Csh &/*csh*/);
    BlockInstruction *Clone(const FileLineColRange &l, std::string_view prefix,
                            gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                            const MultiBlock* multi_parent) const override;
    void ApplyRunningStyle(bool cloning, bool recursive) override;
    Contour LayoutFinalize(Canvas &canvas, const LineAttr &line, const Path &path, double len = -1);
    void PostPosProcess(Canvas &, Chart *) override;
    void Draw(Canvas &canvas) const override;
};

using ArrowLabelList = UPtrList<ArrowLabel>;

struct PathScore
{
    /** how successful we were in avoiding blocks. 
     * We get points for avoiding the following
     * - 1 point for the blocks to go around (in bbs_blocks) (includes start/end blocks if a waypoint is set for them)
     * - 2 points for the cover of other arrows in 'block'
     * - 4 points for the expanded area of the starting block (if a waypoint is set for start due to a fix direction)
     * - 8 points for the expanded area of the ending block (if a waypoint is set for end due to a fix direction)
     * 0 if no path was found. For now 15 for horizontal/vertical/grid routed arrows, if a path was found.
     * This is then more like a bitfield.*/
    unsigned index = 0; 
    double   turn = 0;   ///<The amount of turns in the path, if found
    double   length = 0; ///<The length of the path, if found. This is the straight line distance, not considering any curviness or corners. Just to compare goodness of paths.
    double   shift = 0;  ///<How much the ends of the path are shifted. This is a weighed more than the path length, so it is better not to shift even at the expense of a bit more length.
};

struct PathScoreComp
{
    const double factor; //0 is length only, 1 is turns only - but we don't actually have these extremes ever
    constexpr explicit PathScoreComp(double f) noexcept : factor(f <= 0 ? 0.00001 : f >= 1 ? 0.99999 : f) {}
    bool operator()(const PathScore& a, const PathScore& b) const {
        if (a.index != b.index) return b.index < a.index; //larger index is better
        const double mlen = std::max(std::min(a.length, b.length), 10.);
        const double mturn = std::max(std::min(a.turn, b.turn), 10.);
        const double ashift = a.shift * 5;
        const double bshift = b.shift*5;
        const double aval = factor * a.turn / mturn + (1 - factor) * (a.length + ashift)/ mlen;
        const double bval = factor * b.turn / mturn + (1 - factor) * (b.length + bshift)/ mlen;
        return aval < bval;
    }
};

static inline constexpr double deoverlap_distance_factor = 0.6; //less distance to start with - looks better

/** An arrow between blocks.*/
class Arrow : public BlockInstruction
{
    static constexpr double THRESHOLD = 0.1; ///<Arrow paths are simplified to this granularity (in pixels)
protected:
    Arrow(const Arrow &a, const FileLineColRange &l, std::string_view prefix);
    std::string display_name; ///<For debugging and error message purposes
public:
    struct Ending
    {
        std::array<AlignTo, 2> pos; ///<The actual position of the end
        FileLineCol file_pos;       ///<The location of the end in the input file.
        bool is_block = false;      ///<Indicates whether the arrow ends at a block. (it is set if the ending is a block or a coordinate referencing a single block in both dirs.)
        bool from_coordinate;       ///<True if this end was specified as a coordinate.
        double distance_pixel = 0;  ///<The distance from the block to start/stop the arrow.
        double distance_percent = 0;///<The distance from the block to start/stop the arrow.
        XY offset_pixel_user;       ///<The offset specified by the user after the direction (valid only if is_block is true)
        XY offset_pixel;            ///<The offset specified by the user plus de-overlap offset
        XY offset_percent;          ///<The offset in percentage of the block specified by the user after the direction (valid only if is_block is true)
        StringWithPos port;         ///<The port on the block. Empty if not specified.
        StringWithPosNumber dir;    ///<The direction to use. Empty if not specified.
        double dir_distance = -1;   ///<The distance for the direction's waypoint. Parsed only at coordinates. -1 if not present

        mutable bool clip_block;    ///<True, if we need to clip the end to the block. Can only happen if is_block is true.
        mutable Path dir_segment;   ///<For paths with specific direction this is set to the short path from ultimate_end->first/last via. Used during de-overlapping to make arrows avoid this area, if they can.
        mutable bool deoverlap;     ///<True, if we shall de-overlap
    };
    /** Describes our overlap with another arrow.*/
    struct Overlap
    {
        enum EType
        {
            JUST_ENDPOINT, ///<the two arrows just touch their endpoint, but start in different direction
            SAME_TANGENT,  ///<the two arrows touch at their endpoint, start off in the same tangent, but different curvature (no actual overlap)
            OVERLAP,       ///<the two arrows actually start with the same segment of nonzero length overlap
        } type;   ///<Tells us how much the two arrows overlap
        PathPos pos; ///<The point on my path until which we overlap from my start/end. Ignored with SAME_TILL_END
        bool same_ends;///<True if my front/end corresponds to the front/end of the other, respectively.
        enum ERelation
        {
            CLOCKWISE,    //seen from me, i depart clockwise from the other (I am later)
            CCLOCKWISE,   //seen from me, i depart counter-clockwise from the other (I am earlier)
            SAME_TILL_END //The two arrows are same all the way (we are equal)
        } relation;  ///<Shows how I relate to the other arrow
        static constexpr ERelation Opposite(ERelation r) { return r==CLOCKWISE ? CCLOCKWISE : r==CCLOCKWISE ? CLOCKWISE : r; }
    };
    const ArrowType       type;      ///<Our type as specified by the user. Saved for cloning.
    std::array<Ending, 2> ends;      ///<The start and end of the arrow.
    std::vector<ViaAttr>  route_via; ///<The value of the 'via' attributes, if any.
    ArrowLabelList        labels;    ///<Our labels.
    size_t                my_index = 0; ///<Every arrow inserted into BlockCharts::Arrows in PostParseProcess() gets a unique index. Used to break ties of file_pos (coming from A->B,C where the file_pos is the same for the 2 arrows).
    std::array<std::map<Arrow*, Overlap>, 2>
                          overlaps;  ///<Arrows we overlap with in the order of definition. We ourselves are in this list.
    std::array<bool, 2>   adjusted;  ///<If our end has already been adjusted during de-overlapping.
    bool                  redo;      ///<True after arrow re-overlap we need to recalculate layout
    std::array<bool, 2>   linearextend; ///<If true and we start/end at a block and our endpoint is not inside the block, we linearextend us to hit the block
    double                route_order = 0; ///<Specifies the re-layout order after de-overlapping (user can specify floating point numbers, so we allow)

    mutable double repl_lw2 = 0; ///<Half the line width of the arrow to use at de-overlapping. The same as our linewidth except if other arrows are merged to us. Zero if we are not part of a merge group and shall be de-overlapped by ourselves. If negative, we are merged into another arrow.
    mutable double repl_spacing; ///<The spacing needed on the two sides of the arrow. Used only if repl_lw2 is positive.

    bool IsMergedToAnother() const { return repl_lw2 < 0; }
    bool OthersMergedToThis() const { return repl_lw2 > 0; }
    double GetHalfLineWidth() const { return IsMergedToAnother() ? 0 : OthersMergedToThis() ? repl_lw2 : style.line.LineWidth() / 2; }
    double GetSpacing() const { _ASSERT(style.route_arrow_distance); return OthersMergedToThis()  ? repl_spacing : *style.route_arrow_distance; }

    mutable Path path;          ///< The selected path of the arrow.
    mutable Path path_straight; ///< The selected path of the arrow before made curvy and before trimming
    mutable Path path_visible;  ///< The actually visible selected path of the arrow on which we measure label pos.
    mutable Contour clip_ah;    ///< The clip to apply at drawing for arrowheads.
    mutable Contour clip_block; ///< The clip to apply at drawing for blocks we start/end at and for labels
    Arrow(BlockChart &chart, std::string_view n1, ArrowType t, std::string_view n2,
          const FileLineColRange &l, const FileLineColRange &l1, const FileLineColRange &l2);
    ~Arrow() override;
    std::string_view GetDisplayName() const { return display_name; }
    bool AddAttribute(const Attribute &a) override;
    /** Add the attribute names for block type `t` we take to `csh`.*/
    static void AttributeNames(Csh &);
    /** Add a list of possible attribute value names for block type `t` to `csh` for attribute `attr`.*/
    static bool AttributeValues(std::string_view /*attr*/, Csh &/*csh*/);
    bool AmIContent() const override;
    void AddLabels(const BlockInstrList *l);
    BlockInstruction *Clone(const FileLineColRange &l, std::string_view prefix,
                            gsl::owner<AttributeList*> attr, gsl::owner<CloneActionList *> mod,
                            const MultiBlock* multi_parent) const override;
    void ApplyRunningStyle(bool cloning, bool recursive) override;
    bool PostParseProcess(Canvas &canvas, const BlockBlock* hide_by, Numbering &number) override;
    /** Allows ordering of Arrow objects by source location.
     * my_index is used to disambiguate A->B,C arrows, which have equivalent file_pos.*/
    bool earlier(const Arrow &o) const noexcept { return std::tie(file_pos, my_index)<std::tie(o.file_pos, o.my_index); }
    void FinalizeLabel(Canvas &canvas) override;
    AddConstraintResult
        AddConstraints(Canvas & canvas, ConstraintSet &constraints, unsigned from, unsigned to,
                       unsigned parent_index_justify[2], const InternalAlignment *parent_attr_justify[2]) const override;
    unsigned CheckSegmentRoutability(std::array<std::optional<Contour>, 16> &C, unsigned index,
                                     const XY &p1, const XY &p2,
                                     std::string_view t1, std::string_view t2,
                                     const FileLineCol &l1, const FileLineCol &l2) const;
    void PathFound(Canvas &canvas);
    bool DoIBlockOthers() const noexcept { _ASSERT(style.route_arrow_distance); return *style.route_arrow_distance>0; }
    double GetMyBlockDistance() const noexcept { _ASSERT(style.route_arrow_distance); return DoIBlockOthers() ? GetHalfLineWidth() + GetSpacing() * deoverlap_distance_factor : 0; }
    Contour GetMyBlockAreaForDeoverlap(double other_extend, bool is_end) const;
    bool LayoutHorizVert(Canvas &canvas, int horizontal);
    PathScore Layout(Canvas &canvas, Contour &&block);
    void ResetPath() const;
    void PostPosProcess(Canvas &, Chart *) override;
    void Draw(Canvas &canvas) const override;
};

/** Helper structure containing a list of arrows and a list of box names.*/
struct ArrowParseHelper
{
    UPtrList<Arrow> arrows;
    std::list<StringWithPos> boxnames;
};


} //namespace

#endif //BLOCK_ARROWS_H