/*
    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.h The declaration for the GraphChart class.
* @ingroup libgvgen_files */

#ifndef MYGRAPHS_H
#define MYGRAPHS_H

#include "cgencommon.h"
#include "gvgraphs.h"

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

/** A node that can be expanded back to a cluster subgraph. */
class CollapsableNode : public GraphNode
{
public:
    CollapsableNode(Graph &g, std::string_view name) : GraphNode(g, name) { controls.push_back(EGUIControlType::EXPAND); }
};

/** A subgraph that can be collapsed (adds GUI controls)*/
class CollapsableSubGraph : public SubGraph
{
public:
    CollapsableSubGraph(Graph &g, std::string_view n, bool cluster) : SubGraph(g, n, cluster) { controls.push_back(EGUIControlType::COLLAPSE); }
    CollapsableSubGraph(SubGraph *g, std::string_view n, bool cluster) : SubGraph(g, n, cluster) { controls.push_back(EGUIControlType::COLLAPSE); }
};

/** A graph that can contain collapsible subgraphs. */
class CollapsibleGraph : public Graph
{
public:
    using Graph::Graph;
    StyleCoW<GraphStyle> collapsed_edge_style;
    void Collapse(std::set<std::string> &clusters);
};

/** Type to store which cluster of which graph shall be collapsed */
class ClusterCollapseMap : public std::map<std::string, std::set<std::string>>
{
public:
    bool Deserialize(std::string_view &s);
    std::string Serialize() const;
};

/** The chart class for graphs.*/
class GraphChart : public ChartBase<GraphContext>
{
    Agraph_t *orig_lang_G = nullptr;  ///<When we use the original engine, this is the graph we work on.
    void orig_lang_clear();        ///<Clear all values relating to the original engine.
    void orig_lang_ParseText(std::string_view input); ///<Parse text using the original parser.
    void orig_lang_CompleteParse(bool autoPaginate = false,
                                 bool addHeading = true, XY pageSize = XY(0, 0),
                                 bool fitWidth = true, bool collectLinkInfo = false); ///<Layout using the original parser
    void orig_lang_DrawComplete(Canvas & canvas, bool pageBreaks, unsigned page); ///<Draw using the original engine & the "canvas" render engine.

public:
    /** @name Language attributes
    * @{ */
    std::string GetLanguageDescription() const override { return "Graphviz Graph"; } ///<Returns the human-readable (UTF-8) short description
    std::vector<std::string> GetLanguageExtensions() const override { return{"gv", "graph", "dot"}; } ///<Returns a set of extensions in order of preference
    std::string GetLanguageEntityName() const override { return "node"; }
    std::string GetLanguageDefaultText() const override; ///<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 true; } ///<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;
    /** @} */
protected:
    bool had_layout = false;  ///<True if our graph had been laid out.
    void BeforePushScope();   ///<Creates graph or subgraph before opening the scope. Uses last_** variables to determine what to do
public:
    /** @name variables used during parsing
    * @{ */
	NPtrList<SubGraph> parse_stack;        ///<The subgraphs we are in the middle of defining. Empty if we fill the main graph (just for parsing)
    std::string        last_subgraphname;  ///<The name of the lastly opened subgraph (just for parsing)
    FileLineColRange   last_subgraphspos;  ///<The position of the lastly opened subgraph hdr in the file. Invalid if none (just for parsing)
    bool               lst_scope_is_graph; ///<The last opened scope is a top-level graph scope and not a subgraph (just for parsing)
    GraphStyle         last_subgraphattrs; ///<The attributes specified for the last subgraph
    unsigned           cluser_counter;     ///<Counter used to generate "cluster_xxx" subgraph names for the cluster keyword
    unsigned           current_file;       ///<The number of the file under parsing. Used when generating errors with DotError.
    /** @} */

    bool do_orig_lang = false;          ///<When true, we will use the original engine for everything.
    ClusterCollapseMap force_collapse;  ///<Which clusters are collapsed
    std::list<CollapsibleGraph> Graphs; ///<The graphs we put our results in.

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

    void PushContext(const FileLineCol &l) { BeforePushScope(); ChartBase<GraphContext>::PushContext(l); }
    void PushContext(const FileLineCol &l, EContextParse p) { BeforePushScope(); ChartBase<GraphContext>::PushContext(l, p); }
    void PushContext(const FileLineCol &l, EContextParse p, EContextCreate t) { BeforePushScope(); ChartBase<GraphContext>::PushContext(l, p, t); }
    std::unique_ptr<GraphNodePortList> PopContext(); ///<Closes a scope and returns a pointerlist to all nodes within.

    ClusterCollapseMap GetAllCollapsedGUIState() const;
    ClusterCollapseMap GetAllExpandedGUIState() const { return ClusterCollapseMap(); }

    void NewGraph(bool d, bool s, std::string_view name = {});   ///<Starts the parsing of a new graph (called after completing the parse of its header)
    Graph &GetCurrentGraph() { if (Graphs.size()==0) Graphs.emplace_back(true,false); return Graphs.back(); } ///<The current graph we put stuff into.
    void FinalizeGraph(bool keep); ///<Called after completing the parse of a graph. If 'keep' is false, we discard the graph.

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

    GraphNode * CreateNode(Graph &g, std::string_view name);        ///<Creates a new node into the top graph.
    GraphNode * CreateNode(SubGraph &g, std::string_view name); ///<Creates a new node into a subgraph.
    /** Creates an edge between two nodes. */
    GraphEdge *CreateEdge(GraphNode *_1, GraphNode *_2, const std::string &p1, const std::string &p2,
                              const FileLineColRange &port_file_pos1, const FileLineColRange &port_file_pos2);

    bool AddCommandLineArg(const std::string & arg) override;
    void AddCommandLineOption(const Attribute & a) override;
    void SetDefaultFont(const std::string& face, const std::string& lang, const FileLineColRange& l) override;
    bool DeserializeGUIState(std::string_view) override;
    std::string SerializeGUIState() const override;
    bool ControlClicked(Element *, EGUIControlType) override;
    bool ApplyForcedDesign(const string & name) override;
    void ApplyForcedLayout(const string &name);
    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); }

    /** Adds an attribute list (in the format of a GraphStyle) to each of a list of styles.*/
    void AddAttributeListToStyleList(const GraphStyle *s, const std::vector<std::string> *styles);
    bool AddChartOption(const Attribute &a);
    void DoAttributeInstruction(GraphStyle::EGraphElementType t, gsl::owner<GraphStyle*> al);
    void ParseText(std::string_view input, const FileLineCol& first_char) override;
    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 SetToEmpty() override;
};

} //namespace graph

using graph::GraphChart;

#endif