/*
    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 graphchart.cpp The definition for the GraphChart class and all of the "graph" language.
* @ingroup libgvgen_files */

/** @defgroup libgvgen The engine of the graph library
@ingroup libs

 This library offers a wrapper around graphviz.
 It has its own parser and drawing code, but uses graphviz for layout.
 This allows us to add extensions to graphviz, such as subrgaph collapse,
 styles and designs, richer edge symbols, such as => for double line edges
 and coloring and hinting (so that you do not have to remember all attributes).

 More documentation is coming.

*/

/** @defgroup libgvgen_files Files for the graph library.
* @ingroup libgvgen*/


#define _CRT_SECURE_NO_WARNINGS

#include "gvc.h"
#include "canvas.h"
#include "graphchart.h"
#include "gvcsh.h"
#include "graph_parser_compile.h"

using namespace graph;

/** Deserialize the signature from a string. Sets 's' to the first char after us in the string.*/
bool ClusterCollapseMap::Deserialize(std::string_view &s)
{
    //always accept empty string
    if (s.length()==0 || s[0]==0) {
        clear();
        return true;
    }
    ClusterCollapseMap cat;
    //check version is 0
    auto i = DeserializeInt(s);
    if (!i.first || i.second>0) {
        _ASSERT(0);
        return false;
    }
    if (!DeserializeNewline(s)) { //no newline
        _ASSERT(0);
        return false;
    }
    i = DeserializeInt(s); //number of elements
    if (!i.first) {
        _ASSERT(0);
        return false;
    }
    if (!DeserializeNewline(s) && i.second>0) { //no newline
        _ASSERT(0);
        return false;
    }
    for (; s.length() && s.front() && i.second>0; i.second--) {
        auto g = DeserializeLine(s, true);
        if (!g.first) { _ASSERT(0); return false; }
        const std::string graph(g.second);
        auto ii = DeserializeInt(s);
        if (!ii.first) { _ASSERT(0); return false; }
        if (!DeserializeNewline(s)) { _ASSERT(0); return false; }
        auto &here = cat[graph]; //insert new graph
        for (; ii.second>0; ii.second--) {
            auto sub = DeserializeLine(s, true);
            if (!sub.first) { _ASSERT(0); return false; }
            here.emplace(sub.second);
        }
    }
    if (i.second) { //too few lines compared to the count at the beginning
        _ASSERT(0);
        return false;
    }
    operator=(std::move(cat));
    return true;
}

/** Serialize the signature to a string*/
std::string ClusterCollapseMap::Serialize() const
{
    std::string s("0\n"); //version
    s << size() << "\n"; //number of entries
    for (auto &i : *this) {
        s << i.first << "\n";
        s << i.second.size() << "\n";
        for (auto &ii : i.second)
            s << ii << "\n";
    }
    return s;
}

GraphChart::GraphChart(FileReadProcedure *p, void *param, const LanguageCollection* languages)
    : ChartBase(p, param, languages), last_subgraphattrs(EStyleType::ELEMENT, GraphStyle::GRAPH), cluser_counter(0)
{
    Contexts.emplace_back(true, EContextParse::NORMAL, EContextCreate::PLAIN, FileLineCol());  //creates new, plain context
    Designs.emplace("plain", Contexts.back());
}

GraphChart::~GraphChart()
{
    orig_lang_clear();
}


void GraphChart::BeforePushScope()
{
    if (lst_scope_is_graph) {
        lst_scope_is_graph = false;
    } else {
        const bool cluster = strncmp(last_subgraphname.c_str(), "cluster", 7)==0;
        bool created;
        SubGraph *s;
        if (parse_stack.size())
            s = GetCurrentGraph().CreateSubGraph<CollapsableSubGraph>(parse_stack.back(), last_subgraphname, cluster, &created);
        else
            s = GetCurrentGraph().CreateSubGraph<CollapsableSubGraph>(last_subgraphname, cluster, &created);
        parse_stack.push_back(s);
        if (cluster) {
            if (created) {
                bool coll = false;
                auto i = force_collapse.find(GetCurrentGraph().name);
                if (i!=force_collapse.end())
                    coll = std::find(i->second.begin(), i->second.end(), last_subgraphname) != i->second.end();
                s->style = MyCurrentContext().styles["graph"];
                s->style += MyCurrentContext().styles[coll ? "cluster_collapsed" : "cluster"];
            }
            s->AddAttributeList(&last_subgraphattrs, this); //add the attributes specified in [] or :: after the "subgraph" keyword and name
            GetCurrentGraph().AddAttrName(GraphStyle::GRAPH, last_subgraphattrs);
        }
        last_subgraphattrs.Empty();
        last_subgraphname.clear();
        if (!last_subgraphspos.IsInvalid()) {
            parse_stack.back()->SetLineEnd(last_subgraphspos); //Will do nothing for an already existing graph
            last_subgraphspos.MakeInvalid();
        }
    }
}

std::unique_ptr<GraphNodePortList> GraphChart::PopContext()
{
    std::unique_ptr<GraphNodePortList> ret;
    if (parse_stack.size()) {
        ret = parse_stack.back()->CopyOfNodes();
        parse_stack.pop_back();
    }
    ChartBase::PopContext();
    return ret;
}

ClusterCollapseMap GraphChart::GetAllCollapsedGUIState() const
{
    ClusterCollapseMap ret;
    for (auto &g: Graphs)
        ret[g.name] = g.GetAllSubGraphs();
    return ret;
}

void GraphChart::NewGraph(bool d, bool s, std::string_view name)
{
    Graphs.emplace_back(d, s);
    Graph &GG = Graphs.back();
    if (name.size()) GG.name = name;
    else GG.name << "@" << Graphs.size();
    lst_scope_is_graph = true;
    cluser_counter = 0;
    GG.style = MyCurrentContext().styles["graph"].read();
    //Add all attributes in the style to the list of attributes to bind
    for (const auto &s :MyCurrentContext().styles)
        GG.AddAttrName(s.second.read().element_type, s.second.read());
}

///<The current graph we put stuff into.

void GraphChart::FinalizeGraph(bool keep)
{
    if (keep) {
        Graphs.back().collapsed_edge_style = MyCurrentContext().styles["edge_collapsed"];
    } else
        Graphs.pop_back();
}

GraphNode *GraphChart::CreateNode(Graph &g, std::string_view name)
{
    bool created;
    GraphNode * p = g.CreateNode<GraphNode>(name, &created);
    if (created) {
        //keep whatever style the node creation had (particularly label)
        auto s = MyCurrentContext().styles["node"].read();
        s += p->style.read();
        p->style = std::move(s);
    }
    return p;
}

GraphNode *GraphChart::CreateNode(SubGraph &g, std::string_view name)
{
    bool created;
    GraphNode * p = g.graph.CreateNode<GraphNode>(&g, name, &created);
    if (created) {
        //keep whatever style the node creation had (particularly label)
        auto s = MyCurrentContext().styles["node"].read();
        s += p->style.read();
        p->style = std::move(s);
    }
return p;
}


GraphEdge *GraphChart::CreateEdge(GraphNode *_1, GraphNode *_2,
                                      const std::string &p1, const std::string &p2,
                                      const FileLineColRange &port_file_pos1, const FileLineColRange &port_file_pos2)
{
    _ASSERT(_1 && _2);
    _ASSERT(&_1->graph==&_2->graph);
    bool created;
    GraphEdge * p = _1->graph.CreateEdge<GraphEdge>(_1, _2, p1, p2, port_file_pos1, port_file_pos2, &created);
    if (created) {
        p->style = MyCurrentContext().styles["edge"];
        p->style += MyCurrentContext().styles[std::string(p->type.AsText())];
    }
    return p;
}


bool GraphChart::AddCommandLineArg(const std::string & /*arg*/)
{
    return false;
}

void GraphChart::AddCommandLineOption(const Attribute& /*a*/)
{
}

void GraphChart::SetDefaultFont(const std::string &face, const std::string& lang, const FileLineColRange& l) {
    if (face.length()) {
        for (GraphContext& context : Contexts)
            context.SetDefaultFont(face, this, l);
        for (auto& [name, design] : Designs)
            design.SetDefaultFont(face, this, l);
    }
    //We skip calling ChartBase<>::SetDefaultFont()
    Chart::SetDefaultFont(face, lang, l);
}

bool GraphChart::DeserializeGUIState(std::string_view s)
{
    return force_collapse.Deserialize(s);
}

std::string GraphChart::SerializeGUIState() const
{
    return force_collapse.Serialize();
}

bool GraphChart::ControlClicked(Element *arc, EGUIControlType t)
{
    _ASSERT(t==EGUIControlType::COLLAPSE || t==EGUIControlType::EXPAND);
    if (t!=EGUIControlType::COLLAPSE && t!= EGUIControlType::EXPAND)
        return false;
    const std::string *graph, *cluster;
    CollapsableNode *cn = dynamic_cast<CollapsableNode *>(arc);
    if (cn) {
        graph = &cn->graph.name;
        cluster = &cn->name;
    } else {
        CollapsableSubGraph *cs = dynamic_cast<CollapsableSubGraph *>(arc);
        if (cs) {
            graph = &cs->graph.name;
            cluster = &cs->name;
        } else {
            return false; //no element matches 'arc'
        }
    }
    //note: force_collapse is essentially == std::map<std::string, std::set<std::string>>
    auto i = force_collapse.insert({*graph, {}}); //==<iterator to 'graph', bool true if inserted>
    auto ii = i.first->second.insert(*cluster);   //==<iterator to 'cluster' in 'graph', bool true if inserted>
    if (t==EGUIControlType::EXPAND) {
        i.first->second.erase(ii.first);
        if (i.first->second.size()==0)
            force_collapse.erase(i.first);
        return !ii.second; //if we *did* insert, we actually made no change (as we have removed)
    }
    return ii.second;
}

bool GraphChart::ApplyForcedDesign(const string & name)
{
    //this is called before parsing
    auto i = Designs.find(name);
    if (i==Designs.end())
        return false;
    ignore_designs = true;
    Contexts.back().ApplyContextContent(i->second);
    return Chart::ApplyForcedDesign(name);
}

/** Apply a layout algorithm selected by the user on the ribbon or
 * command-line.*/
void GraphChart::ApplyForcedLayout(const string & name)
{
    if (name.length()) {
        const Attribute a("layout", name, FileLineColRange(), FileLineColRange());
        Contexts.back().styles["graph"].write().AddAttribute(a, this);
    }
}

void GraphChart::AddAttributeListToStyleList(const GraphStyle * s,
    const std::vector<std::string>* styles)
{
    if (s==nullptr) return;
    AttributeList *al = new AttributeList;
    for (auto &a : s->attributes)
        al->Append(std::make_unique<Attribute>(a.second.GenerateAttribute(a.first.c_str(), false)));
    ChartBase::AddAttributeListToStyleList(al, styles);
    delete s;
}


/** Adds a chart option. Currently this is only 'pedantic'.
 * If not a chart option, try adding it to the graph/subgraph we are in. */
 bool GraphChart::AddChartOption(const Attribute &a)
{
    if (a.Is("pedantic")) {
        if (bool p; GraphStyle::CheckBoolValue(a.value.c_str(), p))
            MyCurrentContext().pedantic = p;
        else
            GraphStyle::BooleanValueError(a, Error);
        return true;
    }

    //OK not a chart option, add as a subgraph or graph attribute
    if (parse_stack.size()) {
        parse_stack.back()->AddAttribute(a, this);
        GetCurrentGraph().AddAttrName(GraphStyle::GRAPH, a.name);
        return true;
    }
    if (Graphs.size()) {
        GetCurrentGraph().AddAttribute(a, this);
        GetCurrentGraph().AddAttrName(GraphStyle::GRAPH, a.name);
        return true;
    }
    //no graph, it must be a chart option, but we did not recognize it.
    Error.Error(a, false, "Unrecognized chart option. Ignoring it.");
    return false;
}

 /** Invoked during parsing for attribute instructions, like edge [xxx], or node [xxx]. */
 void GraphChart::DoAttributeInstruction(GraphStyle::EGraphElementType t, gsl::owner<GraphStyle*> al)
 {
     if (al==nullptr) return;
     switch (t) {
     case GraphStyle::GRAPH:
         MyCurrentContext().styles["graph"].write() += *al;
         break;
     case GraphStyle::NODE:
         MyCurrentContext().styles["node"].write() += *al;
         break;
     case GraphStyle::EDGE:
         MyCurrentContext().styles["edge"].write() += *al;
         //Also add it to all edge refinement styles to mimic graphviz behaviour
         for (auto &s : MyCurrentContext().styles)
             if (s.second.read().type==EStyleType::DEF_ADD &&
                 s.second.read().element_type==GraphStyle::EDGE)
                 s.second.write() += *al;
         break;
     default:
     case GraphStyle::CLUSTER:
     case GraphStyle::SUBGRAPH:
     case GraphStyle::ANY:
         _ASSERT(0); //should not be called with these as we only have the above 3 kinds of attribute instrs.
         delete al;
         return; //Dont fall through after the switch!
     }
     //List all the attributes present for the respective types of graphviz nodes.
     GetCurrentGraph().AddAttrName(t, *al);
     delete al;
 }

void GraphChart::ParseText(std::string_view input, const FileLineCol& first_char)
{
    current_file = first_char.file;
    if (do_orig_lang)
        return orig_lang_ParseText(input);
    parse_stack.clear();
    last_subgraphname.clear();
    sv_reader<false> reader(input, first_char);
    gv_parser_helper_compile proc_helper;
    graph_compile_parse(*this, proc_helper, reader); //return value ignored
}

/** Collapse the listed named clusters and fill in
* control info in elements.
* Removes the clusters that are not to be found any longer.*/
void CollapsibleGraph::Collapse(std::set<std::string>& clusters)
{
    //determine if we do strict or non strict collapse
    const auto i = style.read().attributes.find("collapse_strict");
    const bool do_strict = strict || (style.read().attributes.end()!=i && i->second.value=="1");
    //first sanitize our list
    //1. return only those clusters that actually exist
    for (auto i = clusters.begin(); i!=clusters.end(); /*nope*/)
        if (GetSubGraph(*i)) i++;
        else clusters.erase(i++);
    //2. if of two clusters on the list one contains the other, collapse only the outer one
    std::list<std::string> do_collapse;
    for (const auto &c : clusters) {
        bool i_am_child = false;
        for (const auto &c2 : clusters)
            if (c!=c2 && GetSubGraph(c2)->GetSubGraph(c)) {
                i_am_child = true;
                break;
            }
        if (!i_am_child) do_collapse.push_back(c);
    }
    //map mapping from deleted nodes to their replacement
    std::map<GraphNode *, GraphNode *> nodes_to_kill;
    for (std::string &c : do_collapse) {
        SubGraph *ret = GetSubGraph(c);
        _ASSERT(ret);
        //create replacement node
        GraphNode *replacement = CreateNode<CollapsableNode>(ret->parent, ret->name, nullptr);
        replacement->style = ret->style; //should be based on cluster_collapse, see PushContext()
        replacement->file_pos = ret->file_pos;
        //take note of which nodes do we replace to the one created above
        //(all nodes in the collapsed cluster incl. in children)
        auto all = ret->GetAllNodes();
        for (auto p: all)
            nodes_to_kill[p] = replacement;
        //Now delete the collapsed subgraph
        if (ret->parent) {
            ret->parent->Remove(ret);
        } else {
            subgraphs.remove(ret); //remains allocated in subgraphs_storage
        }
    }
    if (nodes_to_kill.size()==0)
        return;
    //Now adjust edges
    //For now we keep all of them
    for (auto &e : edges) { //e is an lvalue of a pointer => can be modified
        GraphNode * n1 = e->n1;
        GraphNode * n2 = e->n2;
        auto i = nodes_to_kill.find(e->n1);
        if (i!=nodes_to_kill.end())
            n1 = i->second;
        i = nodes_to_kill.find(e->n2);
        if (i!=nodes_to_kill.end())
            n2 = i->second;
        if (n1 == e->n1 && n2 == e->n2) continue; //this edge is between nodes we do not replace
        //Add a replacement edge only if this edge does not disappear
        //plus skip if we do strict and already have this edge
        if (n1!=n2) {
            auto i = std::find_if(edges.begin(), edges.end(),
                [n1, n2](auto &e) {return e && n1 == e->n1 && n2 == e->n2; });
            if (!do_strict || i==edges.end()) {
                auto ee = CreateEdge<GraphEdge>(n1, n2,
                    n1==e->n1 ? e->port1 : string(), n2==e->n2 ? e->port2 : string(),
                    n1==e->n1 ? e->port_file_pos1 : FileLineColRange(), n2==e->n2 ? e->port_file_pos1 : FileLineColRange(), nullptr);
                ee->type = e->type;
                ee->style = e->style;
                ee->name = e->name;
            } else {
                //edge already exists, add attributes, name, etc
                i->get()->type = e->type;
                i->get()->style = e->style;
                i->get()->style += collapsed_edge_style;
                i->get()->name = e->name;
            }
        }
        e.reset(); //replace in original 'edges' ptr list
    }
    //Kill the edges we removed
    edges.remove(nullptr);
    //Kill the nodes we have removed
    for (auto i : nodes_to_kill) {
        nodes.remove(i.first);
        for (auto &s: subgraphs)
            s->Remove(i.first);
    }
}

void GraphChart::CompleteParse(bool autoPaginate, bool addHeading,
                               XY pageSize, bool fitWidth, bool collectLinkInfo) {
    Canvas canvas(Canvas::Empty::Query);
    pageBreakData.clear();
    //Consider the copyright text
    StringFormat sf;
    sf.Default();
    XY crTexSize = Label(copyrightText, canvas, Shapes, sf).getTextWidthHeight().RoundUp();
    copyrightTextHeight = crTexSize.y;
    if (do_orig_lang) {
        orig_lang_CompleteParse(autoPaginate, addHeading, pageSize, fitWidth, collectLinkInfo);
    } else {
        Canvas canvas(Canvas::Empty::Query);
        for (auto g = Graphs.begin(); g!=Graphs.end(); /*nope*/) {
            auto i = force_collapse.find(g->name);
            if (i!=force_collapse.end()) {
                g->Collapse(i->second);
                if (i->second.size()==0)
                    force_collapse.erase(g->name);
            }

            g->Layout(this);
            if (g->GetGv()==nullptr) {
                g = Graphs.erase(g);
                continue;
            } else {
                //learn what to draw
                g->PseudoRenderFrom(this); //Also fills GG.boundingBox & clears the graph from cgraph;
                if (g->boundingBox.x.Spans()==0 || g->boundingBox.y.Spans()==0) {
                    g = Graphs.erase(g);
                    continue;
                }
                //shift to the end
                g->ShiftBy(XY(-g->boundingBox.x.from, total.y.till));
                //Register all elements in AllElements, AllCovers; calculate control locations
                g->PostPosProcess(canvas, this);
            }
            total += g->boundingBox;
            //Add one page
            pageBreakData.emplace_back(g->boundingBox.UpperLeft(), g->boundingBox.Spans());
            if (pageBreakData.back().wh.x < crTexSize.x) pageBreakData.back().wh.x = crTexSize.x;
            g++;
        }
    }
    total += Block(0, 1, 0, 1); //have at least this size
    if (total.x.till < crTexSize.x) total.x.till = crTexSize.x;
    if (pageBreakData.size()==0)
        pageBreakData.emplace_back(XY(0, 0), XY(0, 0)); //we *must* have one page
    //We keep graphs in the force_collapse list even if they do not really exist.
    for (Graph& g : Graphs) {
        g.AdjustBkArea(total.x);
        if (auto bk = g.GetBkFill())
            bkFill.emplace_back(*bk);
    }
    Error.Sort();
}


void GraphChart::CollectIsMapElements(Canvas& canvas)
{
    for (auto& GG : Graphs)
        GG.GraphCollectIsMapElements(canvas, *this);
}

void GraphChart::RegisterAllLabels()
{
}

void GraphChart::Draw(Canvas & c, bool pageBreaks, unsigned page)
{
    if (page>Graphs.size() || pageBreakData.size()==0 || Graphs.empty()) {
        c.Fill(total, {});
    } else if (do_orig_lang) {
        orig_lang_DrawComplete(c, pageBreaks, page);
    } else if (page) {
        //roll to the wanted page
        auto g = Graphs.begin();
        for (unsigned i = 1; i<page; i++)
            g++;
        g->Draw(c, Shapes, EGraphDrawPass::BACKGROUND);
        g->Draw(c, Shapes, EGraphDrawPass::CLUSTER);
        g->Draw(c, Shapes, EGraphDrawPass::NODE);
        g->Draw(c, Shapes, EGraphDrawPass::EDGE);
        if (auto bk = g->GetBkFill())
            bkFill.emplace_back(*bk);
    } else {
        for (auto& GG : Graphs)
            GG.Draw(c, Shapes, EGraphDrawPass::BACKGROUND);
        if (pageBreaks)
            DrawPageBreaks(c);
        for (auto& GG : Graphs) {
            GG.Draw(c, Shapes, EGraphDrawPass::CLUSTER);
            GG.Draw(c, Shapes, EGraphDrawPass::NODE);
            GG.Draw(c, Shapes, EGraphDrawPass::EDGE);
        }
    }
}

void GraphChart::SetToEmpty()
{
    orig_lang_clear();
    Graphs.clear();
    ChartBase<GraphContext>::SetToEmpty();
    pageBreakData.emplace_back(XY(0, 0), XY(0, 0));
}


/////////////////////////////////////////////////////////////
//original language related stuff - just one graph!!

void GraphChart::orig_lang_clear()
{
    if (orig_lang_G) {
        if (had_layout)
            gvFreeLayout(Gvc, orig_lang_G);
        gvFinalize(Gvc);
    }
    orig_lang_G = nullptr;
}


void GraphChart::orig_lang_ParseText(std::string_view input)
{
    orig_lang_clear();
    //We use GG here just to store canvas and chart
    Graph GG;
    GG._canvas = nullptr;
    GG._chart = this;
    GG._err_msg_accum.clear();
    Graph::_current = &GG;
    agseterrf(Graph::DotError);
    std::string nullterminated(input);
    orig_lang_G = agmemread(nullterminated.c_str());
    agseterrf(nullptr);
    GG._canvas = nullptr;
    GG._chart = nullptr;
    GG._err_msg_accum.clear();
    Graph::_current = nullptr;
}

void GraphChart::orig_lang_CompleteParse(bool /*autoPaginate*/,
    bool /*addHeading*/, XY /*pageSize*/, bool /*fitWidth*/, bool /*collectLinkInfo*/)
{
    if (had_layout) return;
    if (orig_lang_G) {
        //We use GG here just to store canvas and chart
        Graph GG;
        GG._canvas = nullptr;
        GG._chart = this;
        GG._err_msg_accum.clear();
        Graph::_current = &GG;
        agseterrf(Graph::DotError);
        const char * layout = agget(orig_lang_G, "layout");
        int ret = gvLayout(Gvc, orig_lang_G, layout && *layout ? layout : "dot");
        agseterrf(nullptr);
        GG._canvas = nullptr;
        GG._chart = nullptr;
        GG._err_msg_accum.clear();
        Graph::_current = nullptr;
        if (!ret) {
            total.x.from = GD_bb(orig_lang_G).LL.x;
            total.x.till = GD_bb(orig_lang_G).UR.x;
            total.y.from = GD_bb(orig_lang_G).LL.y;
            total.y.till = GD_bb(orig_lang_G).UR.y;
            //This is a fix to some weird neato behaviour
            total.Shift(-total.UpperLeft()); // move upper left to (0,0)
            had_layout = true;
            goto done;
        }
        //fallthrough
    }
    total.x.from = total.x.till = total.y.from = total.y.till = 0;
    orig_lang_clear();
done:
    //Add one page
    pageBreakData.assign(1, PageBreakData(total.UpperLeft(), total.Spans()));
}



void GraphChart::orig_lang_DrawComplete(Canvas & c, bool /*pageBreaks*/, unsigned /*page*/)
{
    if (orig_lang_G && had_layout) {
        //We use GG here just to store canvas and chart
        Graph GG;
        GG._canvas = &c;
        GG._chart = this;
        GG._err_msg_accum.clear();
        Graph::_current = &GG;
        agseterrf(Graph::DotError);
        gvRender(Gvc, orig_lang_G, "canvas", nullptr);
        agseterrf(nullptr);
        GG._canvas = nullptr;
        GG._chart = nullptr;
        GG._err_msg_accum.clear();
        Graph::_current = nullptr;
    }
}


///<Returns a set of extensions in order of preference

std::string GraphChart::GetLanguageDefaultText() const
{
    return
        "#This is the default graph text\n"
        "graph {\n"
        "    node1::First node;\n"
        "    node2::Second node;\n"
        "    node1->node2::Edge;\n"
        "}\n";
}

std::unique_ptr<Csh> GraphChart::CshFactory(Csh::FileListProc proc, const LanguageCollection* languages) const
{
    return std::make_unique<GraphCsh>(proc, languages);
}

std::map<std::string, std::string> GraphChart::RegisterLibraries() const
{
    char version_buffer[200] = "";
    if (version_buffer[0]==0) {
        const char *ver = gvcVersion(Gvc);
        size_t len = strlen(ver);
        strncpy(version_buffer, ver, 199);
        if (len>=199)
            version_buffer[199] = 0;
    }
    return {{std::string("graphviz v") + gvcVersion(Gvc), {}}};
}




