/*
    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 msccsh.cpp The definition of classes for Color Syntax Highlighting.
 * @ingroup libmscgen_files */
/** @defgroup hintpopup_callbacks Callback functions for hint popup listbox symbols
 * @ingroup libmscgen*/

#define _USE_MATH_DEFINES
#include <math.h>
#include <limits>
#include "msccsh.h"
#include "cgen_color.h"
#include "stringparse.h" //for extracting csh out of strings
#include "cgen_attribute.h"  //for CaseInsensitive compares
#include "msc_parser_csh.h"
#include "msc.h"
#include "cgencommon.h"

using namespace std;
using namespace msc;

/** Names of symbols for coloring
 *
 * All keywords shall be repeated here, not only known by the parser.
 * We only color these as keywords where a symbol name is expected (after 'symbol' keyword).
 * Note that any language name is a valid symbol name (resulting in inlining a chart), but those
 * we have to add dynamically during runtime.*/
static const char symbol_names[][ENUM_STRING_LEN] =
{"arc", "rectangle", "...", "text", "cross", "shape", ""};

/** Descriptions of the symbols in the order above */
static const char* symbol_descriptions[] = {
    "This draws a circle or ellipse.",
    "This draws a rectangle.",
    "This draws three small circles one below another, a kind of vertical ellipsys.",
    "This draws just text.",
    "This draws a larger 'X' symbol.",
    "This draws an arbitrary shape defined via the 'defshape' command.",
};

/** Names of extended vertical position designators for coloring
 *
 * All keywords shall be repeated here, not only known by the parser.
 * We only color these as keywords where a extended vertical position designator is expected (e.g., after 'vertical' keyword).*/
static const char extvxpos_designator_names[][ENUM_STRING_LEN] =
{"left", "right", "center", ""};


template<>
const char EnumEncapsulator<Symbol::ESymbolPosType>::names[][ENUM_STRING_LEN] =
{ "", "above", "below", "rightof", "leftof", "around", "" };

template<>
const char* const EnumEncapsulator<Symbol::ESymbolPosType>::descriptions[] = { "",
    "Draw above the referenced element (or the previous element if none referenced).",
    "Draw below the referenced element (or the previous element if none referenced).",
    "Draw right of the referenced element (or the previous element if none referenced).",
    "Draw left of the referenced element (or the previous element if none referenced).",
    "Draw around the referenced element (or the previous element if none referenced).",
};


/** Override this, since we color mscgen attributes differently. */
void MscCsh::AddCSH_AttrName(const CshPos&pos, std::string_view name, EColorSyntaxType color)
{
    const std::set<std::string> *array, empty_names;
    if (color == COLOR_OPTIONNAME) array = &option_names;
    else if (color == COLOR_ATTRNAME) array = &attribute_names;
    else array = &empty_names;
    unsigned match_result = FindPrefix(*array, name);
    bool mscgen_compat_match = false;

    //check mscgen-specific attributes/options
    if (color == COLOR_OPTIONNAME) array = &mscgen_option_names;
    else if (color == COLOR_ATTRNAME) array = &mscgen_attribute_names;
    else array = &empty_names;
    unsigned match_result2 = FindPrefix(*array, name);
    //take the better of the two matches
    if (match_result2 > match_result) {
        match_result = match_result2;
        mscgen_compat_match = true;
    }
    //Honor partial matches only if cursor is right after
    if (pos.end_pos != cursor_pos && match_result == 1)
        match_result = 0;
    switch (match_result) {
    case 2: AddCSH(pos, mscgen_compat_match ? EColorSyntaxType(color+2) : color); return;
    case 0: AddCSH_Error(pos, color == COLOR_OPTIONNAME ? "Unknown chart option." : "Unknown attribute."); return;
    case 1:
        AddCSH(pos, EColorSyntaxType(color+1));
        was_partial = true;
    }
}

//This is called when a string is at the beginning of where an attribute
//is expected and there is no '=' following.
//It can either be a to-be typed attribute or a style name
// we give ATTRNAME or ATTRNAME_PARTIAL for full or partial attr name matches
// and STYLE for no matched
//All-in-all partial matches are only given if the cursor is just after the
//string in question. In this case we also store the partial match in
// MscCsh::partial_at_cursor_pos
/** Override this, since we color mscgen attributes differently. */
void MscCsh::AddCSH_StyleOrAttrName(const CshPos&pos, std::string_view name)
{
    //Since Styles are among attribute names, we start checking these
    const unsigned match_result_style = FindPrefix(Contexts.back().StyleNames, name);
    if (match_result_style==2) {
        AddCSH(pos, COLOR_STYLENAME);
        return;
    }

    unsigned match_result_attr = FindPrefix(attribute_names, name);
    bool mscgen_compat_match = false;

    //check mscgen-specific attributes
    unsigned match_result_mscgen_attr = FindPrefix(mscgen_attribute_names, name);
    //take the better of the two matches
    if (match_result_mscgen_attr > match_result_attr) {
        match_result_attr = match_result_mscgen_attr;
        mscgen_compat_match = true;
    }

    if (pos.end_pos == cursor_pos && match_result_attr == 1) {
        AddCSH(pos, COLOR_ATTRNAME_PARTIAL);
        was_partial = true;
        return;
    }
    if (match_result_attr == 2) {
        AddCSH(pos, mscgen_compat_match ? COLOR_ATTRNAME_MSCGEN : COLOR_ATTRNAME);
        return;
    }
    //assume all styles valid when parsing a proc def
    if (Contexts.back().SkipContent()) {
        AddCSH(pos, COLOR_STYLENAME);
        return;
    }
    if (match_result_style==1 && pos.end_pos==cursor_pos) {
        AddCSH(pos, COLOR_ATTRVALUE);
        was_partial = true;
        return;
    }
    //If we do not type or is it a prefix it add error
    AddCSH_Error(pos, "Unknown attribute or style name.");
}


void MscCsh::AddCSH_EntityOrMarkerName(const CshPos&pos, std::string_view name)
{
    if (EntityNames.find(name) != EntityNames.end())
        AddCSH(pos, COLOR_ENTITYNAME);
    else
        AddCSH(pos, COLOR_MARKERNAME);
}


//This is called when a string is after the keyword "symbol"
// we give KEYWORD or KEYWORD_PARTIAL for full or partial matches
// and STYLE for no matched
//All-in-all partial matches are only given if the cursor is just after the
//string in question. In this case we also store the partial match in
// MscCsh::partial_at_cursor_pos
void MscCsh::AddCSH_SymbolName(const CshPos&pos, std::string_view name)
{
    unsigned match_result = FindPrefix(symbol_names, name);
    if (match_result<2 && languages)
        match_result = std::max(match_result, FindPrefix(languages->GetLanguages(), name));
    if (pos.end_pos == cursor_pos && match_result == 1) {
        AddCSH(pos, COLOR_KEYWORD_PARTIAL);
        was_partial = true;
        return;
    }
    if (match_result == 2) {
        AddCSH(pos, COLOR_KEYWORD);
        return;
    }
    //if no keyword match, not even partial, we emit an error
    string s = "Unknown symbol name. Use one of '";
    for (auto t: symbol_names)
        s.append(t).append("', '");
    if (languages)
        for (auto t : languages->GetLanguages())
            s.append(t).append("', '");
    s.erase(s.length()-3);
    s.append(".");
    AddCSH_Error(pos, std::move(s));
}

void MscCsh::AddCSH_LeftRightCenterMarker(const CshPos&pos, std::string_view name)
{
    switch (FindPrefix(extvxpos_designator_names, name)) {
    case 2: //Full match
        AddCSH(pos, COLOR_KEYWORD); break;
    case 1: //partial match
        AddCSH(pos, COLOR_KEYWORD_PARTIAL); break;
    default:
        _ASSERT(0); FALLTHROUGH;
    case 0:
        AddCSH(pos, COLOR_MARKERNAME);
    }
}


void MscCsh::AddCSH_ExtvxposDesignatorName(const CshPos&pos, std::string_view name)
{
    unsigned match_result = FindPrefix(extvxpos_designator_names, name);
    if (pos.end_pos == cursor_pos && match_result == 1) {
        AddCSH(pos, COLOR_KEYWORD_PARTIAL);
        was_partial = true;
        return;
    }
    if (match_result == 2) {
        AddCSH(pos, COLOR_KEYWORD);
        return;
    }
    //if no keyword match, not even partial, we emit an error
    string s = "Invalid keyword here. Use one of '";
    for (auto t: extvxpos_designator_names)
        s.append(t).append("', '");
    s.erase(s.length()-3);
    s.append(".");
    AddCSH_Error(pos, std::move(s));
}

/** Callback for drawing a symbol before marker names in the hints popup list box.
 * @ingroup hintpopup_callbacks*/
bool msc::CshHintGraphicCallbackForMarkers(Canvas *canvas, CshHintGraphicParam /*p*/, CshHintStore &)
{
    if (!canvas) return false;
    LineAttr line(ELineType::SOLID, ColorType(64,0,255), 1, ECornerType::NONE, 0);
    FillAttr fill(ColorType(64,0,255).Lighter(0.2), EGradientType::UP);
    ShadowAttr shadow(ColorType(0,0,0));
    shadow.offset = 3;
    shadow.blur = 3;

    Contour c(XY(int(HINT_GRAPHIC_SIZE_X*0.3), int(HINT_GRAPHIC_SIZE_Y*0.2)),
              XY(int(HINT_GRAPHIC_SIZE_X*0.3), int(HINT_GRAPHIC_SIZE_Y*0.8)),
              XY(int(HINT_GRAPHIC_SIZE_X*0.8), int(HINT_GRAPHIC_SIZE_Y*0.5)));
    canvas->Shadow(c, shadow);
    canvas->Fill(c, fill);
    canvas->Line(c, line);
    return true;
}

void MscCsh::BeforeYaccParse(std::string&& input, CharByteIndex first_p, CharByteIndex cursor_p)
{
    Csh::BeforeYaccParse(std::move(input), first_p, cursor_p);
    ResetMarkers();
}

void MscCsh::AfterYaccParse()
{
    if (addMarkersAtEnd) {
        hintStatus = HINT_FILLING;
        for (auto &m : MarkerNames)
            AddToHints(CshHint(HintPrefix(COLOR_ATTRVALUE) + m,
                "Marker names defined via the 'mark' command.", EHintType::MARKER,
                true, CshHintGraphicCallbackForMarkers));
        hintStatus = HINT_READY;
    }
    Csh::AfterYaccParse();
}


/** Parse chart text for color syntax and hint info
 *
 * @param [in] input The chart text
 * @param [in] first_char The position of the first character of 'input' in the file we parse.
 *             If we parse a whole file, it shall be {0,0}
 * @param [in] cursor_p The current position of the cursor.
 * @param [in] pedantic The initial value of the pedantic chart option.
   (We ignore it in CSH parsing.)*/
void MscCsh::ParseText(std::string&& input, CharByteIndex first_char, CharByteIndex cursor_p, bool pedantic [[maybe_unused]] )
{
    pedantic_at_start = pedantic;
    //initialize data struct
    BeforeYaccParse(std::move(input), first_char, cursor_p);
    //call parsing
    sv_reader<true> reader(input_text, first_char);
    msc::lexing_state state = mscgen_compat == EMscgenCompat::FORCE_MSCGEN ? msc::lexing_state::mscgen_compat: msc::lexing_state::basic;
    msc_csh_parse(*this, state, reader); //return value ignored
    //Tidy up afterwards
    AfterYaccParse();
}

void MscCsh::ParseDesignText(std::string&& input) {
    //Design libs shall be parsed with EMscgenCompat::AUTODETECT.
    //Save & restore the value
    const EMscgenCompat saved_mscgen_compat = std::exchange(mscgen_compat, EMscgenCompat::AUTODETECT);
    ParseText(std::move(input), CharByteIndex{}, CharByteIndex::invalid(), true);
    mscgen_compat = saved_mscgen_compat;
}



//Here we are Ok with a ContextBase and do not need an MscContext, as we
//use defaultDesign only to get default styles and colors to produce hints.
//Styles are polymorphic, so their AttributeNames() and AttributeValues()
//will return even the MscStyle specific attributes, even if stored in a ContextBase.
MscCsh::MscCsh(Csh::FileListProc proc, const LanguageCollection* languages) :
    Csh(MscContext(true, EContextParse::NORMAL, EContextCreate::PLAIN, FileLineCol()), proc, languages),
    addMarkersAtEnd(false),
    hint_vertical_shapes(false),
    mscgen_compat(EMscgenCompat::AUTODETECT)
{
    ResetMarkers();
    FillNamesHints();
}


void MscCsh::ResetMarkers()
{
    MarkerNames.clear();
    MarkerNames.insert(MARKER_BUILTIN_CHART_TOP_STR);
    MarkerNames.insert(MARKER_BUILTIN_CHART_BOTTOM_STR);
    addMarkersAtEnd = false;
}


/** If our compatibility mode is AUTODETECT, we switch to MSCGEN compatibility mode*/
void MscCsh::SwitchToMscgenCompatMode()
{
    if (mscgen_compat != EMscgenCompat::AUTODETECT) return;
    mscgen_compat = EMscgenCompat::FORCE_MSCGEN;
    SetDesignTo("mscgen", false);
}

/** Add the symbol types to the hints.*/
void MscCsh::AddSymbolTypesToHints()
{
    AddToHints(symbol_names, symbol_descriptions, HintPrefix(COLOR_KEYWORD), EHintType::KEYWORD, nullptr, true);
    AddLanguagesToHints("symbol");
}

/** Add the left/right/center keyword (for symbol positioning) to the hints.*/
void MscCsh::AddLeftRightCenterToHints()
{
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "left",
        "Use this if you want to specify where the left edge shall be positioned.",
        EHintType::KEYWORD, true, CshHintGraphicCallbackForTextIdent,
        CshHintGraphicParam(MSC_IDENT_LEFT)));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "right",
        "Use this if you want to specify where the right edge shall be positioned.",
        EHintType::KEYWORD, true, CshHintGraphicCallbackForTextIdent,
        CshHintGraphicParam(MSC_IDENT_RIGHT)));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "center",
        "Use this if you want to specify where the center shall be positioned.",
        EHintType::KEYWORD, true, CshHintGraphicCallbackForTextIdent,
        CshHintGraphicParam(MSC_IDENT_CENTER)));
}

/** Callback for drawing how to draw a symbol in relation to another element in the hints popup list box.
* @ingroup hintpopup_callbacks*/
bool CshHintGraphicCallbackForSymbolPos(Canvas* canvas, CshHintGraphicParam p, CshHintStore&) {
    if (!canvas) return false;
    const Symbol::ESymbolPosType t = (Symbol::ESymbolPosType)(int)p;
    const LineAttr elem_line(ELineType::SOLID, ColorType::black(), 1, ECornerType::NONE, 0);
    const FillAttr elem_fill(ColorType::lgray(), EGradientType::NONE);
    Block elem(HINT_GRAPHIC_SIZE_X / 3., HINT_GRAPHIC_SIZE_X * 2 / 3., HINT_GRAPHIC_SIZE_Y / 3., HINT_GRAPHIC_SIZE_Y * 2 / 3.);
    canvas->Fill(elem, elem_fill);
    canvas->Line(elem, elem_line);

    const LineAttr line(ELineType::SOLID, ColorType::red(), 1, ECornerType::NONE, 0);
    const double radius_div = t == Symbol::ESymbolPosType::AROUND ? 3. : 8.;
    static_assert(std::numeric_limits<double>::has_quiet_NaN);
    const XY center = ([t]() { switch (t) {
        case Symbol::ESymbolPosType::ABOVE: return XY( 0, -1);
        case Symbol::ESymbolPosType::BELOW: return XY( 0, +1);
        case Symbol::ESymbolPosType::RIGHTOF: return XY(+1, 0);
        case Symbol::ESymbolPosType::LEFTOF: return XY(-1, 0);
        case Symbol::ESymbolPosType::AROUND: return XY(0, 0);
        default: return XY(std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN());
    }}()*0.4 + XY(0.5, 0.5)).Scale(XY(HINT_GRAPHIC_SIZE_X, HINT_GRAPHIC_SIZE_Y));
    if (std::isnan(center.x)) return false;
    const Contour circle(center, HINT_GRAPHIC_SIZE_X / radius_div, HINT_GRAPHIC_SIZE_Y / radius_div);
    canvas->Line(circle, line);
    return true;
}

/** Add the leftof/rightof/above/below/around keyword (for symbol positioning) to the hints.*/
void MscCsh::AddSymbolPosToHints() {
    AddToHints(EnumEncapsulator<Symbol::ESymbolPosType>::names, EnumEncapsulator<Symbol::ESymbolPosType>::descriptions, 
               HintPrefix(COLOR_KEYWORD), EHintType::KEYWORD, CshHintGraphicCallbackForSymbolPos, true);
}

/** Add the left/right keyword (for hspace specification) to the hints.*/
void MscCsh::AddLeftRightHSpaceToHints()
{
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "left comment",
        "Use this to size the left comment area.",
        EHintType::KEYWORD));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "right comment",
        "Use this to size the right comment area.",
        EHintType::KEYWORD));
}

/** Add the vertical types to the hints.*/
void MscCsh::AddVerticalTypesToHints()
{
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "brace",
        "Use this to add a large vertical curly brace, like '}'.",
        EHintType::KEYWORD));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "bracket",
        "Use this to add a large square bracket, like ']'.",
        EHintType::KEYWORD));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "range",
        "Use this to mark a vertical range, like this 'I'.",
        EHintType::KEYWORD));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "pointer",
        "Use this to add a thin vertical arrow pointing to the entity line.",
        EHintType::KEYWORD));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "box",
        "Use this to add a box with vertically typeset text.",
        EHintType::KEYWORD));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "block",
        "Use this to add a vertical block arrow.",
        EHintType::KEYWORD));
}

/** Add the left/right keyword (for hspace specification) to the hints.*/
void MscCsh::AddLeftRightOfToHints() {
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "leftof",
                       "Draw left of the referenced element (or the previous element if none referenced).",
                       EHintType::KEYWORD));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "rightof",
                       "Draw right of the referenced element (or the previous element if none referenced).",
                       EHintType::KEYWORD));
}


/** Add entities defined up to now to the list of hints. */
void MscCsh::AddEntitiesToHints()
{
    for (auto &e : EntityNames)
        AddToHints(CshHint(HintPrefix(COLOR_ENTITYNAME) + e, nullptr, EHintType::ENTITY, true,
            CshHintGraphicCallbackForMscEntities));
}

/** Add chart option names to the list of hints. */
void MscCsh::AddOptionsToHints()
{
    MscChart::AttributeNames(*this, false);
}

/** Add names of chart options that are valid inside a design definition to the list of hints. */
void MscCsh::AddDesignOptionsToHints()
{
    MscChart::AttributeNames(*this, true);
}

/** Add names of chart options and commands that are valid inside a design definition to the list of hints. */
void MscCsh::AddDesignLineBeginToHints()
{
    /** Names and descriptions of keywords valid inside a design definition for coloring/hints.
    *
    * All keywords shall be repeated here, not only known by the parser.
    * We color only these where keywords should come. We also give hints based on these.*/
    static const char * const design_keyword_names[] = {"invalid", nullptr,
        "defstyle", "Define a new style or change existing styles (applicable only in this design).",
        "defcolor", "Define or redefine a color (applicable only in this design).",
        "defproc", "Define a new procedure that can be re-played later (after applying this design).",
        "include", "Include the text of another file in parsing.",
        ""
    };

    AddDesignOptionsToHints();
    AddToHints(design_keyword_names, HintPrefix(COLOR_KEYWORD), EHintType::KEYWORD,
        CshHintGraphicCallbackForKeywords);
}

/** Add keywords to the list of hints. */
void MscCsh::AddKeywordsToHints(bool includeParallel)
{
    /** Names and descriptions of keywords for coloring.
    *
    * All keywords shall be repeated here, not only known by the parser.
    * We color only these where keywords should come. We also give hints based on these.*/
    static const char * const keyword_names_hints[] = {
        "join", "Using this keyword between arrows, you can connect them. Also works for connecting arrows and boxes.",
        "parallel", "If you prepend 'parallel' in front of an element, subsequent elements will be laid besides it (and not strictly below), if possible without overlap.",
        "overlap", "If you prepend 'overlap' in front of an element, subsequent elements will be laid besides it even if overlap occurs. Useful for slanted arrows crossing each other.",
        "box", "Use before a box definition as syntactic sugar.",
        "block", "Use before an an arrow definition to create a block arrow, like 'block a->b;'.",
        "pipe", "Create a pipe between two entities.",
        "nudge", "Add a bit of a vertical space to push subsequent elements lower.",
        "heading", "Force the display of an entity heading for each currently showing entity.",
        "newpage", "Insert a page break here.",
        "set", "Define or set the value of a variable or change the value of a parameter inside a procedure.",
        "defstyle", "Define a new style or change existing styles.",
        "defcolor", "Define or redefine a color.",
        "defdesign", "Define a full or partial chart design.",
        "vertical", "Add a vertical chart element, such as a brace or vertical arrow.",
        "mark", "Note this vertical position and name it so that you can refer to it later in a vertical element definition.",
        "show", "Turn on entities, so that they become visible.",
        "hide", "Turn off entities, so that they do not show from now on.",
        "activate", "Activate entities, so that their entity line becomes wider.",
        "deactivate", "Deactivate entities, switching back to a single entity line.",
        "bye", "Anything specified after this command is treated as comment and is ignored.",
        "hspace", "Add (extra) horizontal spacing between two entities.",
        "vspace", "Add vertical space pushing subsequent elements lower.",
        "symbol", "Add a drawn element (arc, rectangle, etc.) to the chart.",
        "note", "Add a note (floating in a callout) to the previous chart element.",
        "comment", "Add a comment (visible on the side) to the previous chart element.",
        "endnote", "Add an endnote (visible at the bottom of the chart) referring to the previous chart element.",
        "footnote", "Add footnote (visible at the bottom of the page) referring to the previous chart element.",
        "title", "Add a title to the chart.",
        "subtitle", "Add a subtitle to a section of the chart.",
        "defshape", "Define a new shape.",
        "text", "Draws free text.",
        "defproc", "Define a new procedure that can be re-played later.",
        "include", "Include the text of another file in parsing.",
        "if", "Start a conditional statement. Useful only inside procedures.",
        ""
    };

    for (auto i = keyword_names_hints + (includeParallel ? 0 : 6); i[0][0]; i+=2)
        AddToHints(CshHint(HintPrefix(COLOR_KEYWORD)+i[0], i[1], EHintType::KEYWORD, true,
            CshHintGraphicCallbackForKeywords));
}

/** Callback for drawing a symbol before entities in the hints popup list box.
 * @ingroup hintpopup_callbacks*/
bool msc::CshHintGraphicCallbackForMscEntities(Canvas *canvas, CshHintGraphicParam /*p*/, CshHintStore &)
{
    if (!canvas) return false;
    LineAttr line(ELineType::SOLID, ColorType(0,0,0), 1, ECornerType::NONE, 0);
    LineAttr vline(ELineType::SOLID, ColorType(0,0,0), 2, ECornerType::NONE, 0);
    FillAttr fill(ColorType(192,192,192), EGradientType::UP);
    ShadowAttr shadow(ColorType(0,0,0));
    shadow.offset = 2;
    shadow.blur = 0;

    Block b(HINT_GRAPHIC_SIZE_X*0.25, HINT_GRAPHIC_SIZE_X*0.75, HINT_GRAPHIC_SIZE_Y*0.1, HINT_GRAPHIC_SIZE_Y*0.5);
    canvas->Line(XY(HINT_GRAPHIC_SIZE_X/2, HINT_GRAPHIC_SIZE_Y*0.5), XY(HINT_GRAPHIC_SIZE_X/2, HINT_GRAPHIC_SIZE_Y*0.9), vline);
    canvas->Shadow(b, line, shadow);
    canvas->Fill(b, line, fill);
    canvas->Line(b, line);
    return true;
}

void MscCsh::AddVertXPosSyntaxNonSelectableToHints(bool include_at)
{
    const string prefix = include_at ?
                           "\\|"+HintPrefix(COLOR_KEYWORD)+"at\\s()"+HintPrefixNonSelectable() :
                           HintPrefixNonSelectable();
    AddToHints(CshHint(prefix + "<entity> [offset]",
        "Position exactly on the entity line (optionally offset by the specified number of pixels).",
        EHintType::KEYWORD, false));
    AddToHints(CshHint(prefix + "<entity>+ [offset]",
        "Position a bit right of the entity line (optionally offset by the specified number of pixels).",
        EHintType::KEYWORD, false));
    AddToHints(CshHint(prefix + "<entity>- [offset]",
        "Position a bit left of the entity line (optionally offset by the specified number of pixels).",
        EHintType::KEYWORD, false));
    AddToHints(CshHint(prefix + "<entity>++ [offset]",
        "Position a somewhat right of the entity line (optionally offset by the specified number of pixels).",
        EHintType::KEYWORD, false));
    AddToHints(CshHint(prefix + "<entity>-- [offset]",
        "Position a somewhat left of the entity line (optionally offset by the specified number of pixels).",
        EHintType::KEYWORD, false));
    AddToHints(CshHint(prefix + "<entity>-<entity> [offset]",
        "Position to the midpoint between the two entities (optionally offset by the specified number of pixels).",
        EHintType::KEYWORD, false));
    if (include_at)
        AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "at", nullptr, EHintType::KEYWORD, true));
}

void MscCsh::AddArrowSpecifiersToHints()
{
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "lost at",
        "Use the 'lost at' keyword to indicate that the message has been lost.",
        EHintType::KEYWORD, true));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "start before",
        "Use the 'start before' keyword to indicate where the message originates from.",
        EHintType::KEYWORD, true));
    AddToHints(CshHint(HintPrefix(COLOR_KEYWORD) + "end after",
        "Use the 'end after' keyword to indicate where the message is sent to.",
        EHintType::KEYWORD, true));
    AllowAnything();
}

void MscCsh::AddStartBeforeEndAfterContentToHints()
{
    AddEntitiesToHints();
    AddToHints(CshHint(HintPrefixNonSelectable() + "<entity> [+-distance]",
        "Use a number after the entity to adjust the distance of the arrow ending from the entity line.",
        EHintType::KEYWORD, false));

}



/** Helper to find a prefix in a set of strings.
 * returns -1 if txt is ""
 * returns 0 if txt is not in coll
 * returns 1 if txt is a prefix of something in coll, but not equals anything
 * returns 2 if txt equals to something in coll*/
int FindPrefix(const std::set<std::string> &coll, const char *txt)
{
    if (txt == nullptr || txt[0]==0) return -1;
    unsigned ret = 0;
    _ASSERT(strlen(txt)<UINT_MAX);
    const unsigned len = (unsigned)strlen(txt);
    for (auto i = coll.begin(); i!=coll.end(); i++) {
        if (len > i->length()) continue;
        if (strncmp(i->c_str(), txt, len)) continue;
        if (len == i->length()) return 2;
        ret = 1;
    }
    return ret;
}

/** This is called when a hint is found. We override it to set 'addMarkersAtEnd'
 * if we found a marker.
 * Always returns true - so that it can be called in a return statement.*/
bool MscCsh::DoHintLocated(EHintSourceType hsource, std::string_view a_name)
{
    if (hsource == EHintSourceType::MARKER)
        addMarkersAtEnd = true;
    return Csh::DoHintLocated(hsource, a_name);
}

void MscCsh::FillNamesHints()
{

    /** Additional attribute names in mscgen compatibility mode*/
    static const char attr_names_mscgen[][ENUM_STRING_LEN] = {
        "id", "idurl", "arcskip",
        "linecolor", "linecolour", "textcolor", "textcolour", "textbgcolor", "textbgcolour",
        "arclinecolor", "arclinecolour", "arctextcolor", "arctextcolour", "arctextbgcolor", "arctextbgcolour",
        ""
    };

    for (unsigned u = 0; attr_names_mscgen[u][0]; u++)
        mscgen_attribute_names.insert(attr_names_mscgen[u]);

    /** No specific mscgen options. */

    //Put all chart options as hints
    MscChart::AttributeNames(*this, false);
    MscChart::AttributeNames(*this, true);
    //Then move them to option_names_hints
    MoveHintsToOptionNames();

    //Put all element attributes as hints
    BlockArrow::AttributeNames(*this);
    Box::AttributeNames(*this);
    BoxSeries::AttributeNames(*this);
    DirArrow::AttributeNames(*this);
    Divider::AttributeNames(*this, false, true);
    Divider::AttributeNames(*this, false, false);
    Indicator::AttributeNames(*this);
    ParallelBlocks::AttributeNames(*this, true);
    ParallelBlocks::AttributeNames(*this, false);
    Pipe::AttributeNames(*this);
    PipeSeries::AttributeNames(*this);
    SelfArrow::AttributeNames(*this);
    Vertical::AttributeNames(*this);
    EntityCommand::AttributeNames(*this);
    AddHSpace::AttributeNames(*this);
    Marker::AttributeNames(*this);
    Newpage::AttributeNames(*this);
    Note::AttributeNames(*this, true);
    Note::AttributeNames(*this, false);
    Symbol::AttributeNames(*this, 2);
    AddVSpace::AttributeNames(*this);
    EntityApp::AttributeNames(*this);
    //At this point we do not have shapes read from the default lib, so
    //SimpleStyle::Attributenames will not add these.
    AddToHints(CshHint(HintPrefix(COLOR_ATTRNAME) + "shape",
        "Set the shape of the entity.",
        EHintType::ATTR_NAME));
    AddToHints(CshHint(HintPrefix(COLOR_ATTRNAME) + "shape.size",
        "Set the shape size of the entity.",
        EHintType::ATTR_NAME));
    AddToHints(CshHint(HintPrefix(COLOR_ATTRNAME) + "export",
        "Set if styles and colors defined in a procedure remain valid after calling the procedure.",
        EHintType::ATTR_NAME));
    //Then move them to option_names_hints
    MoveHintsToAttrNames();
}


void MscCsh::CleanupAfterDesignlib()
{
    Csh::CleanupAfterDesignlib();
    MarkerNames.clear();
    ResetMarkers();
}
