/*
    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 style.h The declaration of styles (Style and SimpleStyle)
 * @ingroup libcgencommon_files  */

#ifndef STYLE_H
#define STYLE_H

#include <memory>
#include "cgen_attribute.h"
#include "cgen_arrowhead.h"
#include "stringparse.h"
#include "numbering.h"
#include "csh.h"

/** Describes if the style has the `side` attribute and what values are OK */
enum class ESideType {
    NO = 0,       ///<The invalid value
    LEFT_RIGHT,   ///<Only left and right
    ANY           ///<All values acceptable
};

bool IsValidSideValue(ESideType t, ESide v);
bool CshHintGraphicCallbackForSide(Canvas *canvas, CshHintGraphicParam p, CshHintStore &);

/** Describes, how the unqualified "color" attribute shall be interpreted */
enum class EColorMeaning {
    LINE_ARROW_TEXT, ///<unqualified "color" attribute sets line, arrow & text color
    ARROW_TEXT,      ///<unqualified "color" attribute sets arrow & text color
    LINE_VLINE_TEXT, ///<unqualified "color" attribute sets line, vline & text color
    TEXT,            ///<unqualified "color" attribute sets text color
    FILL,            ///<unqualified "color" attribute set fill color
    LINE_FILL,       ///<unqualified "color" attribute set fill and line color
    NOHOW            ///<unqualified "color" attribute is rejected
};

template <class St>
class StyleCoW;
template <class St>
class ContextBase;


/** A base class for classes bringing together style-related attributes.
 * The class 'Style' actually contains no attributes. Each language is
 * free to extend it with actual attributes.
 *
 * An instance of a Style descendant may not *contain* all possible attributes.
 * For example, in msc only arrow arcs have arrowheads, thus for any other element
 * we shall not be able to specify arrowhead attributes. We say the style
 * instance of those elements 'does not contain' an arrowhead attribute.
 *
 * In addition each attribue a Style descendant contains may or may not be set.
 * An attribute that is not set has no value and any such attribute makes
 * the style *incomplete*. Attributes not set can be assigned value via
 * operator += etc.
 *
 * An Style object also reacts differently depending on the context of
 * its use. Styles that are members of chart elements, for example, must be
 * fully specified, since we need a value to all attributes.
 * Chart styles (such as `weak` or `strong`)
 * on the other hand may contain unset attributes - these attributes will not
 * be changed in the arc when we apply the style to an arc. The context of
 * use is captured in the `type` field of Style.
 *
 * Since in a large chart many elements may be of the same style, we have
 * implemented a copy-on-write mechanism. A style instance may be referred to
 * by multiple StyelCoW objects, dynamically creating a new copy whenever the
 * StyleCoW::write() method is called.
 */

class Style
{
protected:
    template <class AllStyles> friend class ContextBase;
    template <class AllStyles> friend class StyleCoW;
    unsigned ref_count = 1;      ///<The reference counter for the copy-on-write mechanism of styles
public:
    EStyleType type;             ///<The context in which this instance is used.
    EColorMeaning color_meaning; ///<How to interpret an unqualified "color" attribute

    explicit Style(EStyleType tt = EStyleType::STYLE) : type(tt), color_meaning(EColorMeaning::NOHOW) {} //Has all the components, but is empty
    Style(const Style &) = default;
    Style(Style &&) = default;
    Style &operator=(const Style&) = default;
    Style &operator=(Style&&) = default;
    virtual ~Style() = default;
    virtual void Empty() {} ///<Clears all attributes from the style.
    virtual bool IsEmpty() const noexcept { return true; } ///<Tells if we have any attriubute set or not
    virtual void MakeCompleteButText() {} ///<Sets all unset attribute to the default, except the text formatting related ones.
    virtual Style &operator +=(const Style &/*toadd*/) { return *this; }
    virtual bool AddAttribute(const Attribute &a, Chart *chart); ///<Adds an attribute. Returns false and emits error if not applicable.
    virtual bool DoIAcceptUnqualifiedColorAttr() const { return false; } ///<True if the style accepts the 'color' attribute.
    virtual void AttributeNames(Csh &csh) const { csh.AddStylesToHints(false, false); } ///<Adds all applicable attribute names to 'csh'
    virtual bool AttributeValues(std::string_view /*attr*/, Csh &) const { return false; } ///<///<Adds all applicable attribute values to 'csh', if 'attr' is an attribute applicable to us (in this case we return true).
};


/** A simple style containing text, line, fill, shadow, numbering and shape attributes.
 * Not all instance must contain all these, a series of flags (members starting with `f_`)
 * indicate which set of attributes the instance contains or not. Attributes not contained
 * will not be accepted via AddAttribute or operator +=.
 */

class SimpleStyle : public Style
{
protected:
    template <class AllStyles> friend class ContextBase;
    template <class AllStyles> friend class StyleCoW;
    SimpleStyle(EStyleType tt, EColorMeaning cm,
        bool t, bool l, bool f, bool s, bool nu, bool shp);
    unsigned ref_count = 1; ///<How many StyleCoW references us.
public:
    LineAttr            line;      ///<The line attributes
    FillAttr            fill;      ///<The fill attributes
    ShadowAttr          shadow;    ///<The shadow attributes
    StringFormat        text;      ///<The text attributes
    OptAttr<bool>       numbering; ///<The value of the 'number' attribute.
    OptAttr<int>        shape;     ///<The shape we shall use when drawing the entity. -1 if no shape to use.
    OptAttr<EArrowSize> shape_size;///<Its size

    EColorMeaning color_meaning; ///<How to interpret an unqualified "color" attribute

    bool f_line;       ///<True if the style contains line attributes.
    bool f_fill;       ///<True if the style contains fill attributes.
    bool f_shadow;     ///<True if the style contains shadow attributes.
    bool f_text;       ///<True if the style contains text attributes.
    bool f_numbering;  ///<True if the style contains the 'number' attributes.
    bool f_shape;      ///<Governs if the style has entity shape attributes

    SimpleStyle(EStyleType tt = EStyleType::STYLE, EColorMeaning cm = EColorMeaning::NOHOW); //Has all the components, but is empty
    SimpleStyle(const SimpleStyle &) = default;
    SimpleStyle(SimpleStyle &&) = default;
    SimpleStyle &operator=(const SimpleStyle &) = default;
    SimpleStyle &operator=(SimpleStyle &&) = default;
    ~SimpleStyle() override = default;
    void Empty() override;
    bool IsEmpty() const noexcept override;
    void MakeCompleteButText() override;
    Style &operator +=(const Style &toadd) override;
    bool AddAttribute(const Attribute &a, Chart *chart) override;
    bool DoIAcceptUnqualifiedColorAttr() const override;
    void AttributeNames(Csh &csh) const override;
    bool AttributeValues(std::string_view attr, Csh &csh) const override;
    void ApplyTextBefore(const StringFormat& sf);

};

/** An attribute with an added reference counter - works with AttrCoW.*/
template <typename Attr>
class ReferencedAttr : public Attr
{
    template <class AllAttrs> friend class AttrCoW;
    unsigned ref_count = 1;
public:
    using Attr::Attr;
};

/** A Copy-on-Write attribute.
 * If we copy it, we just copy a reference.
 * Then, if we modify it, we actually span a copy.
 * Supports a += operator, that first tests if we actually make a change.
 * Attr must be a descendant of OneArrowAttr, must have Empty() AddAttribute(),
 * MakeComplete(), AttributeName(), AttributeValues(), operator+=() and operator==()
 * methods.*/
template <typename Attr>
class AttrCoW
{
protected:
    ReferencedAttr<Attr> *p;
    void Dereference() { if (p==nullptr || --p->ref_count) return; delete p; p = nullptr; }
    AttrCoW(ReferencedAttr<Attr> *q) : p(q) {} //Only for descendants
public:
    AttrCoW() : p(new ReferencedAttr<Attr>) {}
    template <class A> explicit AttrCoW(A a) : p(new ReferencedAttr<Attr>(a)) {}
    template <class A, class B> explicit AttrCoW(A a, B b) : p(new ReferencedAttr<Attr>(a, b)) {}
    template <class A, class B, class C> explicit AttrCoW(A a, B b, C c) : p(new ReferencedAttr<Attr>(a, b, c)) {}
    explicit AttrCoW(const Attr &o) : p(new AttrCoW(o)) { p->ref_count = 1; }
    explicit AttrCoW(Attr &&o) : p(new AttrCoW(std::move(o))) { p->ref_count = 1; }
    AttrCoW(const AttrCoW &o) : p(o.p) { p->ref_count++; }
    ~AttrCoW() { Dereference(); }
    /** Applies another style to us */
    AttrCoW &operator +=(const AttrCoW &o) { AttrCoW<Attr> tmp(*this); tmp.write() += o.read(); if (tmp.read()!=read()) operator=(tmp); return *this; }
    AttrCoW& operator = (const AttrCoW& o) { if (&o!=this) { Dereference(); p = o.p; p->ref_count++; }return *this; }
    AttrCoW &operator = (const Attr &o) { Dereference(); p = new ReferencedAttr<Attr>(o); p->ref_count = 1;  return *this; }
    AttrCoW &operator = (Attr &&o) { Dereference(); p = new ReferencedAttr<Attr>(std::move(o)); p->ref_count = 1;  return *this; }
    /** Take a reference to the MscStyle object for reading */
    const Attr &read() const { return *p; }
    /** Take a reference to the Style object for writing */
    Attr &write() { if (p->ref_count>1) { p->ref_count--; p = new ReferencedAttr<Attr>(*p); p->ref_count = 1; } return *p; }

    void Empty() { AttrCoW<Attr> tmp(*this); tmp.write().Empty(); if (tmp.read()!=read()) operator=(tmp); }
    void MakeComplete() { AttrCoW<Attr> tmp(*this); tmp.write().MakeComplete(); if (tmp.read()!=read()) operator=(tmp); }
    bool AddAttribute(const Attribute &a, Chart*chart, EStyleType t) { AttrCoW<Attr> tmp(*this); bool ret = tmp.write().AddAttribute(a, chart, t); if (tmp.read()!=read()) operator=(tmp); return ret; }
    static void AttributeNames(Csh &csh, std::string_view prefix) { Attr::AttributeNames(csh, prefix); }
    static bool AttributeValues(std::string_view attr, Csh &csh, EArcArrowType t) { return Attr::AttributeValues(attr, csh, t); }
};




/** The arrow attributes are special in that they can not only be contained
 * by an instance of SimpleStyle or not contained, but one can also specify which
 * arrow type may be assigned: none (not contained), types for line arrows,
 * types for block arrows or any arrow type.
 * The 'Arrow' parameter must be a descendant of OneArrowAttr.
 * This class maintains a copy-on-write scheme for arrow attributes.*/
template <typename Arrow>
class SimpleStyleWithArrow : public SimpleStyle
{
protected:
    template <class AllStyles> friend class ContextBase;
    template <class AllStyles> friend class StyleCoW;
    /** Create an empty style that contains only some of the attributes.
    * @param [in] tt The type of style instance.
    * @param [in] cm How an unqualified "color" attribute shall be interpreted.
    * @param [in] a What type of arrows we accept.
    * @param [in] t True if the style shall contain text attributes.
    * @param [in] l True if the style shall contain line attributes.
    * @param [in] f True if the style shall contain fill attributes.
    * @param [in] s True if the style shall contain shadow attributes.
    * @param [in] nu True if the style shall contain the `number` attribute.
    * @param [in] shp True if the style shall contain entity related attributes (shape, shape.size)
    */
    SimpleStyleWithArrow(EStyleType tt, EColorMeaning cm, EArcArrowType a,
          bool t, bool l, bool f, bool s, bool nu, bool shp) :
        SimpleStyle(tt, cm, t, l, f, s, nu, shp), arrow(a), f_arrow(a) {Empty();}
public:
    AttrCoW<Arrow> arrow;   ///<The arrow attributes
    EArcArrowType  f_arrow; ///<Shows which type of arrow attributes the style contains.

    /** Create an empty style that contains all possible attributes.*/
    SimpleStyleWithArrow(EStyleType tt = EStyleType::STYLE, EColorMeaning cm = EColorMeaning::NOHOW) :
        SimpleStyle(tt, cm), f_arrow(EArcArrowType::ANY) {Empty();}
    SimpleStyleWithArrow(const SimpleStyleWithArrow<Arrow> &) = default;
    SimpleStyleWithArrow(SimpleStyleWithArrow<Arrow> &&) = default;
    SimpleStyleWithArrow &operator=(const SimpleStyleWithArrow<Arrow> &) = default;
    SimpleStyleWithArrow &operator=(SimpleStyleWithArrow<Arrow> &&) = default;
    ~SimpleStyleWithArrow() override = default;
    void Empty() override;
    bool IsEmpty() const noexcept override;
    void MakeCompleteButText() override;
    Style &operator +=(const Style &toadd) override;
    bool AddAttribute(const Attribute &a, Chart *chart) override;
    bool DoIAcceptUnqualifiedColorAttr() const override;
    void AttributeNames(Csh &csh) const override;
    bool AttributeValues(std::string_view attr, Csh &csh) const override;
};

template <typename Arrow>
void SimpleStyleWithArrow<Arrow>::MakeCompleteButText()
{
    SimpleStyle::MakeCompleteButText();
    if (f_arrow!=EArcArrowType::NONE) arrow.MakeComplete();
}

template <typename Arrow>
void SimpleStyleWithArrow<Arrow>::Empty()
{
    SimpleStyle::Empty();
    arrow.Empty();
}

template <typename Arrow>
bool SimpleStyleWithArrow<Arrow>::IsEmpty() const noexcept
{
    return SimpleStyle::IsEmpty()
        && (f_arrow==EArcArrowType::NONE || arrow.read().IsEmpty());
}

template <typename Arrow>
Style & SimpleStyleWithArrow<Arrow>::operator +=(const Style &toadd)
{
    SimpleStyle::operator+=(toadd);
    const SimpleStyleWithArrow<Arrow> *p = dynamic_cast<const SimpleStyleWithArrow<Arrow>*>(&toadd);
    if (p==nullptr)
        return *this;
    if (p->f_arrow!=EArcArrowType::NONE && f_arrow!=EArcArrowType::NONE) arrow += p->arrow;
    return *this;
}

template <typename Arrow>
bool SimpleStyleWithArrow<Arrow>::AddAttribute(const Attribute &a, Chart*chart)
{
    if (a.Is("color")) {
        if ((color_meaning==EColorMeaning::LINE_ARROW_TEXT ||
             color_meaning==EColorMeaning::ARROW_TEXT) &&
            f_arrow!=EArcArrowType::NONE) {
            arrow.AddAttribute(a, chart, type);
            SimpleStyle::AddAttribute(a, chart);
            return true;
        }
        return SimpleStyle::AddAttribute(a, chart);
    }
    if (a.Is("line.width")) {
        if (f_arrow!=EArcArrowType::NONE) arrow.AddAttribute(a, chart, type);
        if (f_line) line.AddAttribute(a, chart, type);
        return f_arrow!=EArcArrowType::NONE || f_line;
    }
    if (f_arrow!=EArcArrowType::NONE && (a.StartsWith("arrow") || a.Is("arrowsize")))
        return arrow.AddAttribute(a, chart, type);
    return SimpleStyle::AddAttribute(a, chart);  //adds styles
}

template <typename Arrow>
bool SimpleStyleWithArrow<Arrow>::DoIAcceptUnqualifiedColorAttr() const
{
    if ((color_meaning==EColorMeaning::LINE_ARROW_TEXT ||
         color_meaning==EColorMeaning::ARROW_TEXT) &&
        f_arrow!=EArcArrowType::NONE)
        return true;
    return SimpleStyle::DoIAcceptUnqualifiedColorAttr();
}


/** Add the attribute names we take to `csh`.*/
template <typename Arrow>
void SimpleStyleWithArrow<Arrow>::AttributeNames(Csh &csh) const
{
    if (f_arrow!=EArcArrowType::NONE) {
        arrow.AttributeNames(csh, "arrow.");
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"arrow.*",
            "Adjust the style of the arrowheads.",
            EHintType::ATTR_NAME));
    }
    SimpleStyle::AttributeNames(csh);  //adds style names
}

/** Add a list of possible attribute value names to `csh` for attribute `attr`.*/
template <typename Arrow>
bool SimpleStyleWithArrow<Arrow>::AttributeValues(std::string_view attr, Csh &csh) const
{
    if ((CaseInsensitiveBeginsWith(attr, "arrow") || CaseInsensitiveEqual(attr, "arrowsize")) && f_arrow!=EArcArrowType::NONE)
        return arrow.AttributeValues(attr, csh, f_arrow);
    return SimpleStyle::AttributeValues(attr, csh);
}






/** A Copy-on-Write style.
 * If we copy it, we just copy a reference.
 * Then, if we modify it, we actually span a copy.*/
template <class St>
class StyleCoW
{
protected:
    St *p;
    void Dereference() {if (p==nullptr || --p->ref_count) return; delete p; p=nullptr;}
    StyleCoW(St *q) : p(q) {} //Only for descendants
public:
    template <class A> explicit StyleCoW(A a) : p(new St(std::move(a))) {}
    template <class A, class B> explicit StyleCoW(A a, B b) : p(new St(std::move(a), std::move(b))) {}
    template <class A, class B, class C> explicit StyleCoW(A a, B b, C c) : p(new St(std::move(a), std::move(b), std::move(c))) {}
    explicit StyleCoW(EStyleType tt=EStyleType::STYLE) : p(new St(tt)) {}
    explicit StyleCoW(const St &o) : p(new St(o)) { p->ref_count = 1; }
    explicit StyleCoW(St&& o) : p(new St(std::move(o))) { p->ref_count = 1; }
    explicit StyleCoW(std::unique_ptr<St>&& o) noexcept : p(o.release()) { p->ref_count = 1; }
    StyleCoW(const StyleCoW& o) noexcept : p(o.p) { p->ref_count++; }
    StyleCoW(StyleCoW&& o) noexcept : p(o.p) { o.p = nullptr; }
    ~StyleCoW() {Dereference();}
    /** Applies another style to us */
    StyleCoW& operator +=(const StyleCoW& o) { if (&o != this && !o.read().IsEmpty()) write() += o.read(); return *this; }
    StyleCoW& operator +=(const St& o) { if (&o != p && !o.IsEmpty()) write() += o; return *this; }
    StyleCoW& operator = (const StyleCoW& o) noexcept { if (&o != this) { Dereference(); p = o.p; p->ref_count++; } return *this; }
    StyleCoW& operator = (StyleCoW&& o) noexcept { if (&o != this) { Dereference(); p = o.p; o.p = nullptr; } return *this; }
    StyleCoW &operator = (const St &o) { Dereference(); p = new St(o); p->ref_count = 1;  return *this; }
    StyleCoW &operator = (St &&o) { Dereference(); p = new St(std::move(o)); p->ref_count = 1;  return *this; }
    /** Take a reference to the MscStyle object for reading */
    const St &read() const {return *p;}
    /** Take a reference to the Style object for writing */
    St &write() { if (p->ref_count>1) { p->ref_count--; p = new St(*p); p->ref_count = 1; } return *p; }
    bool is_same_as(const StyleCoW<St>& o) const noexcept { return p==o.p; }
};

/** Empty, abstract base class for style sets. */
class StyleSetBase
{
public:
    /**Adds the attributes contained in a style to 'csh' for hints*/
    virtual void AttributeNamesForStyle(Csh& csh, std::string_view style) const = 0;
    /** Adds the values of an attribute contained in a style to 'csh' for hints*/
    virtual bool AttributeValuesForStyle(std::string_view attr, Csh& csh, std::string_view style) const =0 ;
    /**Creates an exact duplicate of the styleset.*/
    virtual std::unique_ptr<StyleSetBase> Duplicate() const = 0;
    virtual ~StyleSetBase() = default;
};

/** A set of styles associated by name.
 * The set also contains a default style that is returned, when
 * a style name is not found.
 */
template <class St>
class StyleSet : public std::map<std::string, StyleCoW<St>, std::less<>>, public StyleSetBase
{
    StyleCoW<St> defaultStyle; ///<The default style.
public:
    StyleSet() = default; //def style is empty
    explicit StyleSet(const St &a) : defaultStyle(a) {}
    explicit StyleSet(const StyleCoW<St> &a) : defaultStyle(a) {}
    /** Merges a set of styles into us by applying members that are set in `o`*/
    StyleSet &operator +=(const StyleSet&o);
    /** Merges a set of styles into us by applying members that are set in `o`*/
    StyleSet &operator +=(StyleSet &&o);
    const StyleCoW<St> &GetStyle(std::string_view) const;
    void AttributeNamesForStyle(Csh& csh, std::string_view style) const override;
    bool AttributeValuesForStyle(std::string_view attr, Csh& csh, std::string_view style) const override;
    std::unique_ptr<StyleSetBase> Duplicate() const override { return std::make_unique<StyleSet<St>>(*this); }
};

template<class St>
inline StyleSet<St>& StyleSet<St>::operator+=(const StyleSet<St> & o)
{
    for (auto &style : o) {
        auto j = this->find(style.first);
        if (j==this->end())
            this->insert(style);
        else
            j->second += style.second;
    }
    return *this;
}

template<class St>
inline StyleSet<St> &StyleSet<St>::operator+=(StyleSet<St> && o)
{
    for (auto &style : o) {
        auto j = this->find(style.first);
        if (j==this->end())
            this->insert(std::move(style));
        else
            j->second += style.second;
    }
    return *this;
}

/** Look up a style by name. Return the default style if not found.*/
template <class St>
inline const StyleCoW<St> &StyleSet<St>::GetStyle(std::string_view s) const
{
    typename StyleSet<St>::const_iterator i = StyleSet<St>::find(s);
    if (i==StyleSet<St>::end()) return defaultStyle;
    else return i->second;
};

template <class St>
void StyleSet<St>::AttributeNamesForStyle(Csh& csh, std::string_view style) const
{
    auto i = this->find(style);
    if (i!=this->end())
        i->second.read().AttributeNames(csh);
}

template <class St>
bool StyleSet<St>::AttributeValuesForStyle(std::string_view attr, Csh& csh, std::string_view style) const
{
    auto i = this->find(style);
    if (i!=this->end())
        return i->second.read().AttributeValues(attr, csh);
    return false;
}

/** Contains all information of the definition of a procedure parameter. */
struct ProcParamDef
{
    std::string name;          ///<name with the dollar sign
    FileLineCol linenum_name;  ///<location of the name
    bool has_default_value;    ///<True if the parameter has a default value;
    std::string default_value; ///<The default value (without quotation marks and maybe with location escapes)
    FileLineCol linenum_default_value; ///<The location of the default value.
    ProcParamDef() = default;
    ProcParamDef(std::string_view n, const FileLineCol &ln) : name(n), linenum_name(ln), has_default_value(false) {}
    ProcParamDef(std::string_view n, const FileLineCol &ln, std::string_view d, const FileLineCol &ld) : name(n), linenum_name(ln), has_default_value(true), default_value(d), linenum_default_value(ld) {}
};

//These need to be shared pointers, so that we can copy these lists between MscContexts
using ProcParamDefList = SPtrList<ProcParamDef>;

/** When invoking a parameter, this struct is created to hold its name and (optionally) value.*/
struct ProcParamInvocation
{
    const bool has_value;      ///<True if the parameter has a user supplied value (else it has/needs a default)
    std::string value;         ///<value provided by the caller
    FileLineCol linenum_value; ///<location of the value at the caller site
    ProcParamInvocation(const FileLineCol &lv) : has_value(false), linenum_value(lv) {}
    ProcParamInvocation(std::string_view v, const FileLineCol &lv, bool /*is_param*/ = true) :
        has_value(true), value(v), linenum_value(lv) {}
};

using ProcParamInvocationList = UPtrList<ProcParamInvocation>;

/** An instance of an existing variable or procedure parameter, we can use in expressions.*/
struct ProcParamResolved
{
    std::string value;         ///<value (provided by the caller or default)
    FileLineCol linenum_value; ///<location of the value at the caller site
    const bool is_parameter;   ///<True for procedure parameters, false for variables.
};

using ParameterContext = std::map<std::string, ProcParamResolved, std::less<>>;

/** Enumerates how a procedure definition can turn out.*/
enum class EDefProcResult
{
    OK,       ///<OK, a well-formed procedure body
    EMPTY,    ///<OK, but empty procedure body
    PROBLEM,  ///<We had errors in the procedure body - the procedure should not be used.
};

/** Contains information extracted from the source about a procedure - used at invocation.*/
struct Procedure
{
    EDefProcResult status;///<What was the result of defining this procedure.
    bool export_styles = false;   ///<Shall we export styles defined in this proc to the outer namespace?
    bool export_colors = false;   ///<Shall we export styles defined in this proc to the outer namespace?
    std::string name;     ///<Name of the procedure to invoke
    std::string text;     ///<Text of the procedure to invoke - with both the opening '{', and the closing '}' and ';'
    FileLineCol file_pos; ///<The location of the procedure text - the position of the opening '{' of the procedure body.
    ProcParamDefList parameters;
    static std::pair<const ProcParamDef*, const ProcParamDef*> AreAllParameterNamesUnique(const ProcParamDefList &);
    const ProcParamDef *GetProcParamDef(std::string_view name) const;
    std::optional<ParameterContext> MatchParameters(const ProcParamInvocationList &pl, const FileLineCol &invocation, Chart *chart) const;
    std::optional<ParameterContext> MatchParameters(gsl::owner<ProcParamInvocationList *>pl, const FileLineCol &invocation, Chart *chart) const
         { auto ret = MatchParameters(pl ? std::move(*pl): ProcParamInvocationList(), invocation, chart); delete pl; return ret; }
    void AddAttribute(const Attribute &a, Chart &chart);
};

using ProcedureSet = std::map<std::string, Procedure, std::less<>>;


/** An abstract base class for contexts containing style and color definitions and chart option values.
 *
 * A *context* is a set of settings valid at a given place in the file during
 * parse. It includes the current definition of colors, styles and a few global
 * options. Any time you open a brace in the input file, a new context is opened.
 * If you change any value of it, it will get restored at the closing brace.
 * (Individual languages may depart from this behavior.)
 *
 * Contexts are said to be *full* if they contain a setting for all chart
 * options they contain and a complete value sets for all default styles.
 * By definition, only the default Design (plain) is full and any context
 * it is applied to. There is no other way for a context to become full.
 * Contexts encountered during parsing are always full (since they
 * take value from a full design), except the context within a design
 * definition, unless one applies a full design inside the design definition.
 * (For msc this can be done by the 'msc=' option. */
class Context : public ContextParams
{
protected:
    /** This constructor is used to create an empty context or one set to plain.
     * Used an initializing the first context at parse, when starting to record
     * a design, storing a procedure or moving a newly defined design to the design
     * store. We will have an empty text format, no colors, no procedures, no parameters.*/
    Context(bool f, EContextParse p, const FileLineCol &l) :
        ContextParams(f, p), file_pos(l), num_error(0), if_condition(2),
        export_colors(false), export_styles(false) {}
    /** This constructor is used when a context needs to be duplicated due to
     * the opening of a new scope. You can change the parse mode, e.g., when
     * parsing the not-selected branch of an ifthenelse.*/
    Context(const Context &o, EContextParse p, const FileLineCol &l) :
        ContextParams(o.is_full, p), file_pos(l), num_error(o.num_error), if_condition(2),
        text(o.text), colors(o.colors), export_colors(false), export_styles(false) {}
public:
    const FileLineCol file_pos;   ///<The location of the first character of this context in the input file (used to unambiguously differentiate contexts)
    unsigned          num_error;  ///<The number of errors at the beginning of a procedure in this context. Used during parsing and looses significance after we have finished parsing the procedure.
    unsigned          if_condition;///<If we are after an 'if' clause, what was the result of the condition: 0=false, 1=true, 2=error (already reported, none of the branches will fire)
    ParameterContext  parameters; ///<Parameters if we are inside a procedure definition or invocation (in the latter case all have value, too)
    ProcedureSet      Procedures; ///<The procedures specified in this context
    StringFormat      text;       ///<The default text style
    ColorSet          colors;     ///<The set of named colors defined here
    bool              export_colors; ///<Whether to lift the colors defined in this context to the enclosing context on popping this one.
    bool              export_styles; ///<Whether to lift the styles defined in this context to the enclosing context on popping this one.
    virtual void Empty() { text.Empty(); colors.clear();  is_full = false; } ///<Make the context empty
    virtual void Plain(); ///<Set the context to the 'plain' design
    /** Semi copy operator: copy only user-defined context content. If o is full, we overwrite us, else just merge.*/
    void ApplyContextContent(const Context &o);
    /** Semi move operator: move only user-defined context content. If o is full, we overwrite us, else just merge.*/
    void ApplyContextContent(Context &&o);
    virtual const Style *GetStyle4Read(const std::string &name) const = 0; ///<Return a ready only version of a style
    virtual Style *GetStyle4Write(const std::string &name) = 0;  ///<Return a writable copy of a style
    virtual StyleNameSet GetStyleNames() const = 0; ///<Return the names of the styles included
    virtual std::unique_ptr<StyleSetBase> GetACopyOfStyles() const = 0; ///<Return a copy of all styles included
    virtual void AttributeNamesForStyle(Csh& csh, std::string_view style) const = 0; ///<Adds the attributes contained in a style to 'csh' for hints
    virtual bool AttributeValuesForStyle(std::string_view attr, Csh& csh, std::string_view style) const = 0; ///<Adds the values of an attribute contained in a style to 'csh' for hints
};

/** A named catalog of context pointers used to export and import designs.
 * This is needed so that we need to parse design libraries only once and then
 * we can simple re-import the designs into new Chart objects.*/
using ContextPtrCatalog = std::map<std::string, std::shared_ptr<const Context>>;

/** A base class for chart contexts.
 * This class actually contains styles (and a numbering style)
 * 'St' must be a descendant of 'Style'*/
template <class St>
class ContextBase : public Context
{
public:
    StyleSet<St>            styles;      ///<The set of named styles defined here
    NumberingStyle          numberingStyle;///<The numbering style in use
    /** This constructor is used to create an empty context or one set to plain.
     * Used an initializing the first context at parse, when starting to record
     * a design, storing a procedure or moving a newly defined design to the design
     * store.
     * @param [in] f If true, the context contains a value for all styles and attributes (Full)
     * @param [in] p Tells us what components to observe and how to behave during parsing.
     * @param [in] t Tells us with what content to create the context. It should not be 'COPY
     * @param [in] l The first character of the context in the input file.*/
    ContextBase(bool f, EContextParse p, EContextCreate t, const FileLineCol &l) : Context(f, p, l) {
        switch (t) {
        default: _ASSERT(0); FALLTHROUGH;
        case EContextCreate::PLAIN: Plain(); break;
        case EContextCreate::EMPTY: Empty(); break;
        case EContextCreate::CLEAR: numberingStyle.Empty();}}
    /** This constructor is used when a context needs to be duplicated due to
     * the opening of a new scope. You can change the parse mode, e.g., when
     * parsing the not-selected branch of an ifthenelse.*/
    ContextBase(const ContextBase<St> &o, EContextParse p, const FileLineCol &l) :
        Context(o, p, l), styles(o.styles), numberingStyle(o.numberingStyle) {}
    /** Semi copy operator: copy only user-defined context content. If o is full, we overwrite us, else just merge.*/
    void ApplyContextContent(const ContextBase<St> &o);
    /** Semi move operator: move only user-defined context content. If o is full, we overwrite us, else just merge.*/
    void ApplyContextContent(ContextBase<St> &&o);
    void Empty() override; ///<Make the context empty
    void Plain() override; ///<Set the context to the 'plain' design
    const Style *GetStyle4Read(const std::string &name) const override;
    Style *GetStyle4Write(const std::string &name) override;
    StyleNameSet GetStyleNames() const override;
    std::unique_ptr<StyleSetBase> GetACopyOfStyles() const override { return styles.Duplicate(); }
    /** Add the attribute names a particular style can take to hints.*/
    void AttributeNamesForStyle(Csh& csh, std::string_view style) const override { styles.AttributeNamesForStyle(csh, style); }
    /** Add the possible attribute values for an attribute to hints, if allowed to be set for the particular style.*/
    bool AttributeValuesForStyle(std::string_view attr, Csh& csh, std::string_view style) const override { return styles.AttributeValuesForStyle(attr, csh, style); }
};


template <class St>
void ContextBase<St>::Empty()
{
    Context::Empty();
    text.Empty();
    styles.clear();
    numberingStyle.Empty();
}

template <class St>
void ContextBase<St>::Plain()
{
    Empty();
    Context::Plain(); //colors and is_full is set here
    numberingStyle.Reset();
    numberingStyle.pre = "";
    numberingStyle.post = ": ";

    //Ok, now "weak" and "strong" - if we are descendants of SimpleStyle
    St style = St(EStyleType::STYLE); //has everything, but is empty
    styles["weak"] = style;
    styles["strong"] = style; //descendants must fill in
}

template <class St>
void ContextBase<St>::ApplyContextContent(const ContextBase<St>&o)
{
    Context::ApplyContextContent(o);
    if (o.is_full) {
        styles = o.styles;
        numberingStyle = o.numberingStyle;
    } else {
        styles += o.styles;
        numberingStyle += o.numberingStyle;
    }
}

template <class St>
void ContextBase<St>::ApplyContextContent(ContextBase<St> &&o)
{
    if (o.is_full) {
        styles = std::move(o.styles);
        numberingStyle = std::move(o.numberingStyle);
    } else {
        styles += std::move(o.styles);
        numberingStyle += std::move(o.numberingStyle);
    }
    Context::ApplyContextContent(std::move(o));
}

/** Search a style and return it for reading. nullptr if not found*/
template <class St>
inline const Style *ContextBase<St>::GetStyle4Read(const std::string &name) const
{
    auto i = styles.find(name);
    return i==styles.end() ? nullptr : &i->second.read();
}

/** Search a style and return it for writing. nullptr if not found*/
template <class St>
inline Style *ContextBase<St>::GetStyle4Write(const std::string &name)
{
    auto i = styles.find(name);
    return i==styles.end() ? nullptr : &i->second.write();
}

template <class St>
inline StyleNameSet ContextBase<St>::GetStyleNames() const
{
    StyleNameSet ret;
    for (auto &d : styles)
        ret.insert(d.first);
    return ret;
}



#endif //STYLE_H