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

#ifndef BLOCK_CSH_H
#define BLOCK_CSH_H

#include "csh.h"
#include "blockstyle.h"

namespace block {

/** Helper structure to contain a box name and its position in the file.*/
struct CshStringViewWithPos
{
    std::string_view name;
    CshPos file_pos;
};
using CshStringViewWithPosList = std::vector<CshStringViewWithPos>;
struct CshStringWithPos
{
    std::string name;
    CshPos file_pos;
    CshStringWithPos(std::string_view n, const CshPos &r) :name(n), file_pos(r) {}
    CshStringWithPos(const CshStringViewWithPos &o) : name(o.name), file_pos(o.file_pos) {}
};
using CshStringWithPosList = std::vector<CshStringWithPos>;

/** Enumeration listing the potential keywords before the 'use' command.*/
enum EUseKeywords
{
    USE_KEYWORD_NONE = 0,
    USE_KEYWORD_BLOCKS = 1,
    USE_KEYWORD_ARROWS = 2,
    USE_KEYWORD_ALL = USE_KEYWORD_BLOCKS | USE_KEYWORD_ARROWS,
};

constexpr EUseKeywords operator |(EUseKeywords a, EUseKeywords b) { return EUseKeywords(unsigned(a)|unsigned(b)); }
constexpr EUseKeywords operator &(EUseKeywords a, EUseKeywords b) { return EUseKeywords(unsigned(a)&unsigned(b)); }

constexpr EBlockStyleTarget ToEBlockStyleTarget(EUseKeywords a) { 
    _ASSERT(a != EUseKeywords::USE_KEYWORD_NONE); 
    return a == USE_KEYWORD_BLOCKS ? EBlockStyleTarget::Block : a == USE_KEYWORD_ARROWS ? EBlockStyleTarget::Arrow : EBlockStyleTarget::Any;
}

struct UseCommandParseHelper
{
    gsl::owner<AttributeList *> attrs;
    EUseKeywords keywords;
};

/** Additional context information for block language.*/
struct BlockCshContext
{
    std::string prefix; ///<The prefix in the current context
    bool pedantic;      ///<The value of the pedantic option
    int def_shape;      ///<The default shape in this context
    BlockCshContext() = default;
    BlockCshContext(std::string_view p, bool pe, int s) : prefix (p), pedantic(pe), def_shape(s) {}
};

struct BlockMention {
    CshPos pos;
    std::string name;    ///<If prefix is empty, this is the full name, else the name the user specified (latter user for forward references)
    std::optional<std::string> prefix; ///<The prefix the user mentioned 'name' in. If empty optional, 'name' is already a resolved, full name.
    BlockMention(const CshPos &p, std::string_view n, std::optional<std::string_view> pr = {})
        : pos(p), name(n), prefix(pr ? std::string{*pr} : std::optional<std::string>{}) {}
};

/** Coloring for Block. */
class BlockCsh : public Csh
{
    /** Pushes the parent name to the context stack duplicating the other fields.*/
    void PushContext2() { bool p = Context2.back().pedantic; int s = Context2.back().def_shape;  Context2.emplace_back(parent_name, p, s); }
public:
    using BlockList = std::map<std::string, int>;

    static BlockList Prefix(const BlockList& b, std::string_view pref) { BlockList ret; std::string p = std::string(pref)+"."; for (auto& [n, sh] : b) ret.insert(ret.end(), std::pair{ p+n, sh }); return ret; }
    static BlockList UnPrefix(const BlockList& b, std::string_view pref) { BlockList ret; std::string p = std::string(pref)+"."; for (auto& [n, sh] : b) { _ASSERT(n.starts_with(p)); ret.insert(ret.end(), std::pair{ n.substr(p.size()), sh}); } return ret; }

    std::vector<BlockCshContext> Context2;  ///<Contains a stack of additional context information
    std::string parent_name;                ///<A temporary storage of a name from the header of the block until the opening of the scope for its content.
    BlockList Blocks;                       ///<A list of all defined entity (full name) mapping to its shape number. -1 means the 'box' shape. Only the first block of such name is stored, if there are many, 'second' is set to -2.
    std::map<std::string, std::pair<BlockList, int>> Templates; ///<A list of templates and their contained blocks, plus their shape
    std::string prefix_at_hint_loc;         ///<If addEntityNamesAtEnd is not empty, this captures the prefix of the context we hint at.
    bool hadProcReplay;                     ///<If we had a replayed procedure so far. In this case we accept all block names as valid.
    std::vector<BlockMention> BlockMentions; ///<All the COLOR_ENTITYNAME blocks, with the full name of the entity they refer to (used for entity rename).
    explicit BlockCsh(Csh::FileListProc proc, const LanguageCollection* languages);
    ~BlockCsh() override = default;
    std::unique_ptr<Csh> Clone() const override { return std::make_unique<BlockCsh>(*this); }

    /** Push the context stack copying what was on top.*/
    void PushContext() { Csh::PushContext(); PushContext2(); }
    /** Push the context stack  with an empty context.*/
    void PushContext(bool f, EContextParse p) { Csh::PushContext(f, p); PushContext2(); }
    void PopContext() { Csh::PopContext(); Context2.pop_back(); }  ///<Pop the context stack.

    /** Return the prefix in the current context.*/
    std::string GetCurrentPrefix() const { return Context2.size() ? Context2.back().prefix : std::string(); }
    /** Returns true if 'pedantic' is currently turned on.*/
    bool GetPedantic() const noexcept { return Context2.size() && Context2.back().pedantic; }
    /** Prefix a name in the current context. */
    std::string PrefixName(std::string_view n) const { return Context2.empty() || Context2.back().prefix.empty() ? std::string(n) : StrCat(Context2.back().prefix, '.', n); }

    /** Take the child blocks of the last element on the list and add them to
     * all blocks before. Also destroys 'l'. No op if l has less than 2 elements.
     * We skip all elements with a dot (usually comes from 'multi') both when detecting
     * the last element and when adding blocks. */
    void CreateBlocksFromLast(const CshStringWithPosList* l);
    /** Deletes a block. Name is from current context. */
    void DeleteBlock(std::string_view name);
    /** Make the block a template. We remove its name (and also content names) from the Blocks list
     * and create a template from it.
     * In addition, we turn any ENTITYNAME_FIRST entries entirely *within* 'p' to MARKERNAME.*/
    void Templatize(std::string_view name, const CshPos& p);
    /** Make the listed blocks templates. We remove their names (including content) from the Blocks list
     * and create a template from them. We only create templates from names not containing a dot.*/
    void Templatize(gsl::owner<CshStringWithPosList*> l);
    /** Create a copy of the content of 'from' under the name 'to' (both local names as spelled
     * by the user). 'from' can also be a template. If we have both a template and a block with
     * this name, we use the template (as per lang rules). Returns true if 'from' is not found.
     * Note: we assume 'to' is already added to 'Blocks', so there we only set the shape.
     * Note2: We also add CSH entries for from and to (which may be the same).*/
    bool Copy(std::string_view from, std::string_view to, const CshPos& from_pos, const CshPos& to_pos);
    /** Adds the '.front', '.back' and '.2' ... '.n-1' to each element in the list.*/
    gsl::owner<CshStringWithPosList*> MakeMulti(int num, gsl::owner<CshStringWithPosList*> l);

    // Inherited via Csh
    void FillNamesHints() override;
    void AddDesignOptionsToHints() override;
    void AddOptionsToHints() override;

    void AddKeywordsToHints(bool include_dir, bool include_join_around, bool include_multi_copy);
    void AddBlockKeywordsToHints() { AddKeywordsToHints(false, false, false); }
    void AddLineBeginToHints();
    void AddUseKeywordsToHints(EUseKeywords e= USE_KEYWORD_NONE);
    void AddCloneActionKeywordsToHints();
    void AddBeforeToHints();
    void AddMarkExtendToHints(bool mark, bool extend);
    void AddPortsToHints(int shape);
    void AddWhatToUpdateToHints(bool include_keywords);

    void BeforeYaccParse(std::string&& input, CharByteIndex first_pos, CharByteIndex cursor_pos) override;
    void ParseText(std::string&& input, CharByteIndex first_char, CharByteIndex cursor_p, bool pedantic) override;
    bool HandleBlockNamePlus(std::string_view aname, std::string_view avalue = {}, EHintSourceType rewrite_source = EHintSourceType::ENTITY);
    template <typename Value>
    std::pair<int, typename std::map<std::string, Value>::const_iterator> SearchName(const std::map<std::string, Value>& Names, std::string_view string, bool may_be_new, bool allow_magic) const;
    std::pair<int, std::string_view> SearchEntityName(std::string_view string, bool may_be_new) const;
    void AddEntityNamesAtTheEnd(std::string_view msg) override;
    void AfterYaccParse() override;
    /** Called when the user updates attributes of blocks, like "a" or "below x a" or "a [color=red]".
     * We will define the blocks listed if not yet defined*/
    void UpdateBlocks(const CshStringViewWithPosList *);
    /** Drop all block mentions in this range (assumed to be added last)*/
    void DropBlockMentions(CshPos pos);

    void AddCSH_LineBeginSoloString(CshPos pos, std::string_view string);
    void AddCSH_AfterMultiPartial(CshPos pos, std::string_view string);
    int AddCSH_BlockName(const CshPos&pos, std::string_view name, EColorSyntaxType cinstead = EColorSyntaxType::COLOR_ERROR);
    void AddCSH_LocalBlockName(const CshPos&pos, std::string_view name);
    int AddCSH_BlockNameOrNew(const CshPos&pos, std::string_view name);
    void AddCSH_BlockNameError(const CshPos& pos, std::string_view name, int err);
    void UpdateCSH_ArrowEndAtLineBegin(const CshPos& pos, std::string_view arrow_end);
    bool AddCSH_NewBlock(const CshPos &pos, std::string_view pname, int shape);
    bool AddCSH_NewBlock(int shape) { return AddCSH_NewBlock({}, {}, shape); }
    void AddCSH_EntityName(const CshPos&, std::string_view) { _ASSERT(0); }//Use AddCSH_BlockName() instead. }
    void AddCSH_BlockNameAsAttrValue(const CshPos &pos, std::string_view name);
    void AddCSH_AttrValue_CheckAndAddEscapeHint(const CshPos &pos, std::string_view value, std::string_view name) override;
    void AddCSH_NameList(const CshStringViewWithPosList* l, const CshPos& pos, EColorSyntaxType cname,
                         EColorSyntaxType cbetween, EColorSyntaxType cnumber = EColorSyntaxType::COLOR_ERROR,
                         EColorSyntaxType cinstead = EColorSyntaxType::COLOR_ERROR);

    EntityNameUnderResult EntityNameUnder(long char_index) const override;
    std::string_view AskReplace(std::string_view full_entity_name, int byte_pos) const override;
    std::string ReplaceEntityName(std::string_view full_entity_name, int byte_pos,
                                          std::string_view replace_to,
                                          long lStart, long lEnd) const override;
};

/** Finds a name in a map<string, X>.
 * @param [in] Names The map to look for the name
 * @param [in] name The name of the entity as typed by the user. We search namespaces and apply
 *             our current prefix. For unqualified names (no dot), we also do magic lookup if
 *             'allow_magic' is set.
 * @param [in] may_be_new This is a place where we may either reference an existing entity but may
 *             also define a new entity. If the name contains no dots (magic lookup applies) and
 *             it does not exist in the current namespace (only magic lookup could yield a hit),
 *             but it appears multiple times in other namespaces (so that magic lookup would
 *             yield an ambiguous result), we do not return -3, but -4, to allow defining a new
 *             entity.
 * @param [in] allow_magic If set, then unqualified names are found in other namespaces if unambiguous.
 * @Returns
 * - -1 if this is a valid name from the current context
 * - -2 if this is the prefix of a partial name.
 * - -3 if this is a matching name, but multiple blocks match and it is ambiguous.
 * - -4 if this is not a valid name (or not found).
 *
 * Also returns the iterator for the name, if we don't skip content & found a valid unambiguous match.*/
template <typename Value>
std::pair<int, typename std::map<std::string, Value>::const_iterator> BlockCsh::SearchName(const std::map<std::string, Value>& Names, std::string_view name, bool may_be_new, bool allow_magic) const {
    //When recording a procedure, we accept anything as a valid name.
    if (Contexts.back().SkipContent()) return { -1, Names.end() };
    //In the string we may have a partial prefix. For example, if our parent is A.B.C,
    //and we have a sibling named 'xyz', typing any of 'A.B.C.xyz', 'B.C.xyz', 'C.xyz' or 'xyz'
    //may match it. We also have to find any of 'A.B.C.xy', 'B.C.xy', 'C.xy', 'xy' or
    //'A.B.C.x', 'B.C.x', 'C.x', 'x' as partial matches. Also, 'A.', 'A.B.', etc.

    //We do a linear search
    std::pair<int, typename std::map<std::string, Value>::const_iterator> ret = { -4, Names.end()}, magic = {-4, Names.end() };
    int prefix_found_size = -1;
    allow_magic &= name.find('.') == std::string_view::npos; //magic only for unqualified names
    for (auto i = Names.begin(); i != Names.end(); i++) {
        //see if bname (or any sub-component of it) starts with name
        //bname="AAA.BBB.CCC" will match name="A", "AA", "AAA", "AAA.BB", "AAA.BBB.CCC",
        //but also name="B", "BBB", "BBB.C", "BBB.CCC" or "C", "CCC"
        size_t pos = 0;
        while (!std::string_view(i->first).substr(pos).starts_with(name)) {
            pos = i->first.find('.', pos);
            if (pos == std::string_view::npos) break;
            pos++;
        }
        if (pos == std::string_view::npos) continue;
        if (pos + name.length() == i->first.length()) {
            //We are a good full match at the end.
            //1. Check if we have no dot: for i->first=AAA.BBB.CCC and name=CCC we can match
            //   if there is only one name ending in CCC. This is 'magic' lookup.
            if (allow_magic) {
                if (magic.first == -1) magic.first = -3;
                else if (magic.first == -4) magic = { -1, i };
            }
            //2. Check if current prefix works.
            //   That is for i->first=AAA.BBB.CCC and name=BBB.CCC we must be within namespace AAA
            if (pos == 0 || GetCurrentPrefix().substr(0, pos - 1) == std::string_view(i->first).substr(0, pos - 1)) {
                if ((int)pos < prefix_found_size) continue;
                //If there is a match already existing, mark us as ambiguous
                else if ((int)pos == prefix_found_size) ret = { -3, i };
                //If there were a partial match or no match at all and now we have a full, use it.
                else {
                    ret = { -1, i };
                    prefix_found_size = pos;
                }
            }
        }
        else if (i->first.find('.', pos + name.length()) == std::string::npos) {
            //no dot in the name after us - A partial match.
            //If there were no match so far, use it.
            if (ret.first == -4) ret = { -2, i };
        }
    }
    switch (ret.first) {
    default: _ASSERT(0); return { -4, Names.end() };
    case -1: return ret;
    case -2: if (magic.first == -1) return magic; else return ret; //partial found: return magic only if something good is found.
    case -3: return ret; //ambiguous: multiple found by non-magic lookup
    case -4: if (may_be_new && magic.first == -3) return ret; else return magic; //not found: only return -3 by magic if this cannot be a new entity
    }
}

}; //namespace

#endif