/*
    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 blockchart.h The declaration for the BlockChart class.
* @ingroup libblock_files */

#ifndef BLOCK_GRAPHS_H
#define BLOCK_GRAPHS_H

#include <unordered_map>
#include "cgencommon.h"
#include "block_arrows.h"
#include "blockcsh.h"

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

/** Enumerates the compilation sections of BlockChart.*/
enum class ESections
{
    PARSE,         ///<Text parsing
    POSTPROCESS,   ///<Post processing: label calculation, attribute adjustment.
    LAYOUT_BLOCK,  ///<Laying out blocks
    LAYOUT_ARROWS, ///<Laying out pointers
    POSTPOS,       ///<Tidy up after layout
    DRAW,          ///<Drawing
    MAX_SECTION    ///<Signals the largest section +1
};

/** Progress tracking for BlockCharts.
 * No arc sections, just bulk ones.*/
class BlockProgress : public Progress<ESections, ESections::MAX_SECTION>
{
public:
    explicit BlockProgress(ProgressCallback cb = nullptr, void *d=nullptr, double g=1)
        : Progress({
            {ESections::PARSE, "Parsing"},
            {ESections::POSTPROCESS, "Post Parse Process"},
            {ESections::LAYOUT_BLOCK, "Laying out Blocks"},
            {ESections::LAYOUT_ARROWS, "Routing arrows"},
            {ESections::POSTPOS, "Post Position Processing"},
            {ESections::DRAW, "Drawing"},
        }, ProgressBase::EPreferredAbortMethod::EXCEPTION, cb, d, g) {}
};

struct reverse_compare
{
    bool operator()(std::string_view a, std::string_view b) const noexcept
    {
        return std::lexicographical_compare(a.rbegin(), a.rend(), b.rbegin(), b.rend());
    }
    using is_transparent = void;
};

/** A hash table to map unique block names to block pointers*/
using BlockNameCatalog = std::unordered_map<std::string, BlockBlock*, svhash, std::equal_to<>>;
/** A hash table to map potentially non-unique block names to block pointers.*/
using MultiBlockNameCatalog = std::unordered_multimap<std::string, BlockBlock*, svhash, std::equal_to<>>;
/** A map mapping potentially non-unique block names to block pointers.
 * Sorting is reverse, to be able to search based on postfixes.*/
using ReverseBNameMultiMap = std::multimap<std::string, BlockBlock*, reverse_compare>;
/** The state of a collapse/expand command from the GUI. */
struct ElementGUIState {
    bool collapsed;                //If true, the GUI wants to collapse this element, else expand.
    bool element_exists = false;   //Set to true during compilation if one element recognizes itself
};

struct InlinedGUIState {
    std::string gui_state;
    bool element_exists = false;
};

/** A GUI Collapse/Expand catalog. Maps a unique name to a 'collapsed' flag. */
struct GUIState {
    std::unordered_map<std::string, ElementGUIState> collapse;
    std::unordered_map<std::string, InlinedGUIState> inlined;
};

struct ArrowAttrHelper {
    gsl::owner<AttributeList*> al;
    gsl::owner<BlockInstrList*> bl;
    void Free() {delete al; delete bl; al=nullptr; bl=nullptr;}
};

struct LayoutStat {
    union {
        struct {
            size_t no_must_constraints;
            size_t no_rejected_constraints;
            size_t no_removed_constraints;
            size_t no_pruned_constraints;
            size_t no_constraints;
            size_t no_removed_variables;
            size_t no_variables;
            size_t no_passes;
        };
        size_t stats[8] = {}; //zero initialize all size_t
    };
    void clear() noexcept { std::ranges::fill(stats, 0); }
    LayoutStat &operator += (const LayoutStat &o) noexcept { for (size_t i = 0; i<sizeof(stats)/sizeof(stats[0]); i++) stats[i] += o.stats[i]; return *this; }
    std::string Dump(std::chrono::steady_clock::duration time, size_t no_blocks, size_t from, size_t to) const;
};


    /** A group of arrow ends(false = start, true = end) that overlap */
struct ArrowEndGroup
{
    struct ArrowEnd
    {
        Arrow* pA;
        bool is_end;
        bool is_shifting; //If true, this end needs to be shifted at de-overlapping
        bool is_originally_shifting; //This is used to preserve the "is_shifting" across permutations
        bool operator<(const ArrowEnd &o) const noexcept { return std::tie(pA, is_end)<std::tie(o.pA, o.is_end); }
        const Arrow::Ending& ending() const noexcept { return pA->ends[is_end]; }
    };
    std::vector<ArrowEnd> ends; 
    XY shift_vector;    ///<A 1-length vector perpendicular to the arrow endings. This is the direction to shift arrow ends.
    void Add(Arrow* pA, bool is_end);
    void ApplyShift();
    size_t number_of_permutations() const;
    void next_permutation(unsigned permutation_no);
};

using ArrowToScore = std::unordered_map<Arrow*, PathScore>;

/** The Block Diagram chart class.*/
class BlockChart : public ChartBase<BlockContext, BlockProgress>
{
public:
    /** @name Language attributes
    * @{ */
    std::string GetLanguageDescription() const override { return "Block Diagram"; } ///<Returns the human-readable (UTF-8) short description
    std::vector<std::string> GetLanguageExtensions() const override { return{"block"}; } ///<Returns a set of extensions in order of preference
    std::string GetLanguageEntityName() const override { return "block"; }
    std::string GetLanguageDefaultText() const override { return "#This is the default block text\n"; } ///<Returns the default text of the chart in UTF-8
    bool GetLanguageHasAutoheading() const override { return false; } ///<True if the chart has automatic headings
    bool GetLanguageHasElementControls() const override { return false; } ///<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;
    /** @} */
    BlockStyle parent_style;         ///<Attributes the parent applies to its content (adds to BlockContext::running_style)
    Parent current_parent;           ///<The parent block. Set while parsing a block header. Used to initialize 'BlockContext::parent' when opening a scope afterwards for content.
    std::string copying_as;          ///<When parsing a clone action list this holds the target name. Any new blocks defined during this shall have this as prefix.
    BlockInstrList StashedElements;  ///<Temporary storage for arrows during the 'arrow' nonterminal while parsing
protected:
    unsigned                 block_seq;   ///<The next seq num for unique names
    std::map<std::string, std::unique_ptr<BlockBlock>, std::less<>> Templates;
    bool has_xy_correlation = false;  ///<True if the chart has correlation between X and Y dimensions, e.g., the width of an element depends on the height of another.
public:
    /** How many bulk layout block items do we register per block. This is a granularity thing as
     * we don't actually progress layout by blocks, but by constraints.*/
    static constexpr unsigned block_layout_bulk_per_block = 1000;
    //Names should be declared first, so at destruction, all blocks are destroyed before them.
    BlockNameCatalog       BlockNames_Unique;  ///<Contains all the unique block names assigned by the user mapping to the block object. Unique names are never empty.
    ReverseBNameMultiMap   BlockNames_Full;    ///<Contains all the full block names assigned by the user mapping to the block object. May contain the empty name.
    MultiBlockNameCatalog  BlockNames_Original;///<Contains all the original block names assigned by the user mapping to the block object. Does not contain the empty name.
    std::vector<BlockElement*> DrawOrder;      ///<A reference to elements in the order they need to be drawn.
    GUIState               GUI_State;          ///<Collapse/expand info

    BlockBlock          Blocks;             ///<The main block containing all other blocks.
    std::vector<Arrow*> Arrows;             ///<A reference to all arrows in the order of their definition. Filled in Arrow::PostParseProcess()

    LayoutStat layout_stat;                 ///<Here we collect stats during layout

	static constexpr bool draw_margins = false;         ///<Whether we should draw the margins in thin dotted lines
	static constexpr double arrow_scale = 0.5;          ///<How much we scale arrowheads compared to the default (the size for signalling charts is such that 'small' is the acceptable default. Here we scale down and take 'normal' as default.)
	static constexpr double arrow_ambigous_percent = 20;///<How much going around a block may be longer than the other way to skip considering the alternative
    static constexpr double def_distance = 5;           ///<How much shall arrows avoid blocks.
    static constexpr double def_distance_manhattan = 7; ///<How much shall arrows avoid blocks for Manhattan routed arrows. It is more than 5, which would make normal sized arrows look bad on a sharp turn.
	static constexpr double def_distance_cp = 10;       ///<How much shall arrows avoid start/stop blocks if a compass point is specified
	static constexpr double def_margin = 10;            ///<The default external margin
	static constexpr double def_imargin = 10;           ///<The default internal margin
	static constexpr double def_multi_offset = 5;       ///<The default offset for multi-blocks
    EAlignPrio conflict_report;    ///<Warn of alignment conflicts of this level or more serious. We always report Explicit_low_low and above as errors.

    BlockChart(FileReadProcedure *p, void *param, const LanguageCollection* languages);
    ~BlockChart();

    std::unique_ptr<BlockInstrList> PopContext();            ///<Closes a scope

    static std::unique_ptr<Chart> Factory(FileReadProcedure *p, void *param, const LanguageCollection* languages)
        { return std::make_unique<BlockChart>(p, param, languages); }

    bool AddCommandLineArg(const std::string & arg) override;
    void AddCommandLineOption(const Attribute & a) override;
    bool DeserializeGUIState(std::string_view) override;
    std::string SerializeGUIState() const override;
    bool ControlClicked(Element *, EGUIControlType) override;
    bool ApplyForcedDesign(const string & name) override;
    bool GetPedantic() const noexcept override { return Contexts.empty() ? true : Contexts.back().pedantic.value_or(true); }
    void SetPedantic(bool pedantic) noexcept override { if (Contexts.size()) Contexts.back().pedantic = pedantic; Chart::SetPedantic(pedantic); }
    std::string_view GetCurrentPrefix() const noexcept { return Contexts.size() ? Contexts.back().GetPrefix() : std::string_view{}; }

    bool AddAttribute(const Attribute&);
    /** Add chart options applicable to designs.*/
    bool AddDesignAttribute(const Attribute&);
    /** Adds the list of chart options to a Csh object. */
    bool AddRunningStyleAttribute(const Attribute & a, EUseKeywords e);
    static void AttributeNames(Csh &, bool designOnly);
    /** Adds the list valid values for a chart options to a Csh object. */
    static bool AttributeValues(std::string_view attr, Csh &csh);
    /** Adds an attribute list to each of a list of styles.*/
    void AddAttributeListToStyleListWithFilePos(gsl::owner<AttributeList *> al, const StringWithPosList *styles);
    void AddAttributeListToRunningStyle(gsl::owner<AttributeList *> al, EUseKeywords e);
    void SetXYCorrelation() { has_xy_correlation = true; }
    void ParseText(std::string_view input, const FileLineCol& first_char) override;
    gsl::owner<BlockInstrList *> AppendInstructionToList(gsl::owner<BlockInstrList *>, gsl::owner<BlockInstruction *> b);
    void MoveArrowLabels(BlockInstrList &al);
    std::string CreateUniqueName(std::string_view name_original, const FileLineCol &l)
    { return StrCat(name_original, std::string_view(l.Print()).substr(2), std::to_string(++block_seq)); }
    gsl::owner<Attribute *> CreateAttribute(std::string_view name, std::string_view value,
                                            const FileLineColRange &ln, const FileLineColRange &lv) const;
	static bool BlockNameToCreatePart(std::string_view &name);
	std::unique_ptr<BlockBlock> AutoCreateBlock(std::string_view name, const FileLineColRange &l, const AttributeList *al=nullptr,
                                                const AlignmentAttr * align = nullptr);
	Arrow *CreateArrow(std::string_view n1, ArrowType t, std::string_view n2,
                       const ArrowAttrHelper &ah, const FileLineColRange &l,
                    const FileLineColRange &l1, const FileLineColRange &l2);
    void StashArrows(const StringWithPosList* l1, ArrowType t, const StringWithPosList* l2,
                     ArrowAttrHelper &&ah, const FileLineColRange &l);
    BlockInstrList *ExtractStashed();
    BlockBlockList *CreateMulti(int num, gsl::owner<BlockBlockList*> b, const FileLineColRange &l);
    BlockInstruction *CreateJoin(const FileLineColRange &l, gsl::owner<StringWithPosList*> blks,
                                 gsl::owner<AttributeList*> al = nullptr);
    gsl::owner<CopyParseHelper*> ResolveCopyHelper(gsl::owner<CopyParseHelper*> helper) ;
    BlockBlock *CreateCopy(gsl::owner<CopyParseHelper*> helper);
    void CreateTemplate(gsl::owner<BlockBlock*> block);
	std::unique_ptr<BlockBlock>
		UpdateBlock(std::string_view name, const FileLineColRange &l,
                    const AttributeList *al, const AlignmentAttr * align=nullptr);
	std::unique_ptr<BlockInstrList>
		UpdateBlock(const StringWithPosList *spl, const AttributeList *al,
                    const AlignmentAttr * align = nullptr);
	std::array<const BlockBlock *, 3>
        GetBlockByName(std::string_view n, const Parent& used_from, bool liberal, bool only_valid,
                       const FileLineCol *loc=nullptr) const;
    const BlockBlock *GetBlockByNameWithError(std::string_view n, const FileLineCol &l,
                                              const Parent& used_from, std::string_view whattoignore);
    bool LayoutWithGLPK(Canvas &canvas, unsigned from, unsigned to);
	void IncreaseTotal(const Block &b) { total += b; }
	void IncreaseTotal(const XY &p) { total += p; }
	OptAttr<double> GetCoord(const AlignTo &a, bool y, const BlockBlock *parent) const;
    std::vector<ArrowEndGroup> CheckArrowOverlap();
    std::unordered_map<Arrow*, std::vector<Arrow*>> MergeFullOverlapping(std::vector<ArrowEndGroup>& groups);
    ArrowToScore Redo(Canvas& canvas, const std::vector<Arrow*>& redo);
    std::pair<ArrowToScore, std::vector<Arrow*>> RedoLayout(Canvas& canvas, bool bail_out_early);
    void HandleArrowOverlap(Canvas& canvas, std::vector<ArrowEndGroup>&& groups);

    void CompleteParse(bool autoPaginate = false,
                       bool addHeading = true, XY pageSize = XY(0, 0),
                       bool fitWidth = true, bool collectLinkInfo = false) override;
    void CollectIsMapElements(Canvas & canvas) override;
    void RegisterAllLabels() override;
    void Draw(Canvas& canvas, bool pageBreaks, unsigned page) override;
    void DrawComplete(Canvas & canvas, bool pageBreaks, unsigned page) override;
    void SetToEmpty() override;
};

/** Resolves all block names in the series.
 * @param blocks The list of blocks to resolve.
 * @param [in] spec_blocks The blocks for first, prev, next, last. Either or all may be null meaning
 *                    No such block. The last block ([4]) is the block we do the resolving for.
 *                    It may also be null if we do resolving for e.g., an arrow.
 * @param [in] break_msg If non empty, we emit an error if a block name refer to a "break block". Else we
 *                       remove that silently. You can use %b to replace to the block name .
 * @param [in] ambiguous_msg If a block name is a special name, but the corresponding element in 'spec_blocks'
 *                          is nullptr, we emit this message and clear the block. Else we silently clear the block.
 *                          You can use %b to replace to the block name and %p to replace to first or last.
 * @param [in] spec_block_msg If non empty, we emit an error if a block name is a special block instead of
 *                            resolving it. In this case the spec_block[0..3] is ignored. The name is removed
 *                            from the list.
 *                            You can use %b to replace to the block name.
 * @param [out] text The text to append to the attribute description (if any)
 * @param [out] justify A pointer to the OptAlignAttr::justify if we process an alignment attr. We set it
 *                      if a block refers solely to the opposite side of the prev or next block.
 *                      So set it to a nonzero value if the sides are opposite.
 * @returns if we have emitted any error.*/
template <typename str_pos_vector>
inline bool BlockElement::Resolve(str_pos_vector &blocks, SpecBlockPtrs spec_blocks,
                                  std::string_view break_msg, std::string_view ambiguous_msg,
                                  std::string_view spec_block_msg, std::string *text, bool *justify)
{
    //remove duplicates from the list
    remove_duplicates(blocks, [](auto &a, auto &b) {return a.name==b.name; });

    //Now translate 'first', 'prev', 'next', 'last', 'parent' to actual block names.
    //Give error if we do not have 'prev' or 'next' (because we are first, last) or if we are the same.
    //Do not give error if it was not the user that specified the token, but it comes from some default.
    bool ret = false;
    for (unsigned u = 0; u<spec_block_names.size(); u++) {
        bool not_yet_added_text = true;
        //if prev_next_first_or last is a break block, remove them from the blocks list
        for (auto &sp : blocks)
            if (sp.name == spec_block_names[u]) {
                if (spec_block_msg.length() && spec_blocks[u]==nullptr) {
                    ret = true;
                    sp.name.clear();
                    std::string msg(spec_block_msg);
                    auto at = msg.find("%b");
                    if (at!=std::string::npos)
                        msg.replace(at, 2, spec_block_names[u]);
                    chart.Error.Error(sp.file_pos.start, msg);
                } else if (dynamic_cast<const BlockBreak*>(spec_blocks[u])) {
                    sp.name.clear();
                    if (break_msg.length()) {
                        std::string msg(break_msg);
                        auto at = msg.find("%b");
                        if (at!=std::string::npos)
                            msg.replace(at, 2, spec_block_names[u]);
                        chart.Error.Error(sp.file_pos.start, msg);
                        ret = true;
                    }
                } else if (spec_blocks[u]==nullptr || spec_blocks[u] == this) { //if 'prev' or 'first' is applied to the first block or 'next' and 'last' to the last one...
                    if (sp.file_pos.start.IsValid()) {//...and we know the location...
                        if (ambiguous_msg.length()) {  //...and this attribute is explicitly set at the block, not from a style (so we have ambiguous_msg) ...
                            std::string msg(ambiguous_msg);
                            //...then emit an error.
                            auto at = msg.find("%b");
                            if (at!=std::string::npos)
                                msg.replace(at, 2, spec_block_names[u]);
                            at = msg.find("%p");
                            if (at!=std::string::npos)
                                msg.replace(at, 2, (u<2 ? "first" : "last"));
                            chart.Error.Error(sp.file_pos.start, msg);
                            ret = true;
                        }
                        //else (if this comes from a style or we for some reason don't know the location, ignore silently.
                    }
                    sp.name.clear();
                } else {
                    sp.name = spec_blocks[u]->name_unique;
                    if (not_yet_added_text && text)
                        text->append("', ").append(spec_block_names[u]).append(" being '").append(spec_blocks[u]->name_display);
                    not_yet_added_text = false;
                    //can only be true if ('a' is left/top & other is Max) or ('a' is right/bottom and the other is Min)
                    if (justify &&          //if we align to opposite sides ...
                        (u==1 || u==2) &&   //...of our next or prev block...
                        blocks.size()==1)   //...and there is jut one element in the block list
                        *justify = true;    //...then may be an attribute impacted by justification
                }
            }
    }
    //remove cleared blocks.
    remove_if(blocks, [](const auto&a) {return a.name.length()==0; });
    return ret;
}


/** Checks if all blocks listed in blocks actually exits. If not they are removed.
 * @param blocks The list of blocks to check and prune.
 * @param whattoignore What shall we say, what will be ignored if the block is not found?
 * @param [in] bool resolve_hidden What to do with hidden blocks.
 * @param [in] self_msg If non empty, we emit an error if a block name refers to us.
 *                      Else we silently ignore and remove from list. Use %b to refer to the block name.
 *                      and %B to refer to the block that is collapsed and hides the one in question.
 * @param [in] keep_last If true, we keep the last one of blocks mentioned several times, else the first.
 * @returns true if blocks was not empty but it has has become so.*/
template <typename str_pos_vector>
inline bool BlockElement::CheckExists(str_pos_vector &blocks, std::string_view whattoignore,
                                      HandleHidden resolve_hidden, std::string_view self_msg, bool keep_last)
{
    if (blocks.size()==0) return false;
    //Check that the block actually exists.
    for (auto &sp : blocks)
        if (sp.name.length()) {
            auto p = chart.GetBlockByNameWithError(sp.name, sp.file_pos.start, parent, whattoignore);
            if (nullptr==p) {
                sp.name.clear();
                continue;
            }
            if (p==this) {
                if (self_msg.length()) {
                    std::string msg(self_msg);
                    auto at = msg.find("%b");
                    if (at!=std::string::npos)
                        msg.replace(at, 2, sp.name);
                    chart.Error.Error(sp.file_pos.start, msg);
                }
                sp.name.clear();
            } else if (p->hidden_by) {
                if (resolve_hidden.message.size()) {
                    std::string msg(resolve_hidden.message);
                    auto at = msg.find("%b");
                    if (at!=std::string::npos)
                        msg.replace(at, 2, sp.name);
                    at = msg.find("%B");
                    if (at!=std::string::npos)
                        msg.replace(at, 2, p->hidden_by->name_display);
                    chart.Error.Error(sp.file_pos.start, msg);
                }
                switch (resolve_hidden.action) {
                default:
                case HandleHidden::Keep: sp.name = p->name_unique;  break;
                case HandleHidden::Remove: sp.name.clear(); break;
                case HandleHidden::ToParent: sp.name = p->hidden_by->name_unique; break;
                }
            } else
                sp.name = p->name_unique;
        }
    //remove cleared blocks.
    remove_if(blocks, [](const auto&a) {return a.name.length()==0; });
    //remove duplicates
    remove_duplicates(blocks, [](auto &a, auto &b) {return a.name==b.name; }, keep_last);
    return blocks.size()==0;
}


};//namespace

using block::BlockChart;

#endif