/*
    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 cgen_attribute.h Declaration of Attribute and classes for line, fill, shadow and width attributes.
 * Contains imports from the contour namespace. Contains a PtrList and other helpers for enums.
 * @ingroup libcgencommon_files */


#if !defined(ATTRIBUTE_H)
#define ATTRIBUTE_H

#include <string>
#include <utility>
#include <list>
#include <algorithm>
#include <charconv>
#include "cgen_color.h"
#include "csh.h"
#include "contour.h"

using contour::XY;
using contour::Range;
using contour::Block;
using contour::TRMatrix;
using contour::Edge;
using contour::Path;
using contour::PathPos;
using contour::SimpleContour;
using contour::Contour;

using contour::DoubleMap;
using contour::fmod_negative_safe;

using contour::EXPAND_MITER;
using contour::EXPAND_BEVEL;
using contour::EXPAND_ROUND;
using contour::EXPAND_MITER_BEVEL;
using contour::EXPAND_MITER_ROUND;

using contour::EForceClockwise;
using contour::ECloseType;

using std::string;

/** Helper class, holds two doubles.
 * Typically used to store left/right margin values.
 * Sometimes during computation the values may be reversed or
 * mean something different. */
struct DoublePair
{
    double left;   ///<The margin typically on the left side
    double right;  ///<The margin typically on the right side
    /** Swaps the two sides. */
    void swap() {std::swap(left, right);}
};

/** Supply string with a good append ("<<") operator.
 * When a lot of small strings are to be added and/or the result is expected
 * to be longer than 16 bytes (what small string optimization can handle),
 * use StrCat() instead.*/
template <class T> requires std::is_arithmetic_v<T>
string &operator <<(string &s, T t) { return s.append(std::to_string(t)); }
template <class T> requires std::is_arithmetic_v<T>
string&& operator <<(string&& s, T t) { return std::move(s.append(std::to_string(t))); }
string& operator <<(string& s, bool t) = delete;
string&& operator <<(string&& s, bool t) = delete;
inline string& operator <<(string& s, char c) { s.push_back(c); return s; };
inline string&& operator <<(string&& s, char c) { s.push_back(c); return std::move(s); }
inline string& operator <<(string& s, const char *t) { return s.append(t); } //needed or it converts to bool
inline string&& operator <<(string&& s, const char* t) { return std::move(s.append(t)); }
inline string &operator <<(string&s, std::string_view t) { return s.append(t); }
inline string&& operator <<(string&& s, std::string_view t) { return std::move(s.append(t)); }
inline string& operator <<(string& s, std::string&& t) { return s.append(std::move(t)); }
inline string&& operator <<(string&& s, std::string&& t) { return std::move(s.append(std::move(t))); }

/** Return length the common prefix in two strings (case insensitive). */
unsigned CaseInsensitiveCommonPrefixLen(std::string_view a, std::string_view b) noexcept;
/** Checks if `b` is a prefix of `a`.
 * Returns
 * - 0 if `a` does not begin with `b`
 * - 1 if a begins with `b`
 * - 2 if a == b
 *
 * - Rule 1: the empty string ("") begins with a nullptr pointer string, but is not equal to it
 * - Rule 2: every string begins with both the empty string ("") or a nullptr pointer string*/
int CaseInsensitiveBeginsWith(std::string_view a, std::string_view b) noexcept;
/** Checks if the two strings are identical (case insensitive)*/
inline bool CaseInsensitiveEqual(std::string_view a, std::string_view b) noexcept
    {return a.length()==b.length() && CaseInsensitiveBeginsWith(a,b)==2;}

bool CaseInsensitiveSmaller(std::string_view a, std::string_view b) noexcept;

/** Checks how an attribute name ends (dot-sensitive).
 * Note that attribute names can be segmented with dots. This function
 * checks if one or more of the last segments are equal to `a`.
 * Thus 'line.width' ends with 'width' or 'line.width' but not with 'dth'.*/
bool CaseInsensitiveEndsWith(std::string_view base, std::string_view a) noexcept;

/** Checks if 'base' contains 'a' in a case-insensitive manner.
 * @return the first char of the match if 'a' substring of 'base'
 * If 'a' is empty or equal to 'base' we return an optional filled with 0.*/
std::optional<size_t> CaseInsensitiveContains(std::string_view base, std::string_view a) noexcept;


template <typename ... StrViews>
size_t CombinedLen(char, StrViews ... views);

template <typename ... StrViews>
inline size_t CombinedLen(std::string_view a, StrViews ... views)
{ return a.length()+CombinedLen(views...); }

template <typename ... StrViews>
inline size_t CombinedLen(char, StrViews ... views)
{
    return 1+CombinedLen(views...);
}

template<>
inline size_t CombinedLen(std::string_view a)
{
    return a.length();
}

template<>
inline size_t CombinedLen(char)
{ return 1; }


template <typename ... StrViews>
void CopyTo(std::string::iterator to, char a, StrViews ... views);

template <typename ... StrViews>
inline void CopyTo(std::string::iterator to, std::string_view a, StrViews ... views)
{
    CopyTo(std::copy(a.begin(), a.end(), to), views...);
}

template <typename ... StrViews>
inline void CopyTo(std::string::iterator to, char a, StrViews ... views)
{
    *to = a;
    CopyTo(std::next(to), views...);
}

template <>
inline void CopyTo(std::string::iterator to, std::string_view a)
{
    std::copy(a.begin(), a.end(), to);
}
template <>
inline void CopyTo(std::string::iterator to, char a)
{
    *to = a;
}

/** Creates a string from N views. */
template <typename ... StrViews>
inline std::string StrCat(StrViews ... views)
{
    std::string ret;
    ret.resize(CombinedLen(views...));
    CopyTo(ret.begin(), views...);
    return ret;
}

/** Helper structure to contain a string and its position in the file.*/
struct StringWithPos
{
    std::string name;
    FileLineColRange file_pos;
    StringWithPos() = default;
    StringWithPos(std::string_view n, const FileLineColRange &r) :name(n), file_pos(r) {}
};
using StringWithPosList = std::vector<StringWithPos>;

/** Converts a range of strings to a single string enclosed in 's.*/
template <std::ranges::range R> requires(requires(R r) { std::string() += *r.begin(); })
std::string PrintStrings(R &&r)
{
    string ret;
    for (auto& s : r) {
        if (s.length() == 0) continue;
        if (ret.length()) ret += "', '";
        else ret = "'";
        ret += s;
    }
    ret += "'";
    return ret;
}


/** Converts 's' to a floating point or signed or unsigned integer
 * value. Returns true on error including the case when not all
 * characters of s can be consumed.*/
template <typename T> requires(std::is_arithmetic_v<T> && !std::is_const_v<T>)
inline bool from_chars(std::string_view s, T& value) noexcept
{
    if (s.length()==0) return true;
    if (s.front()=='+') s.remove_prefix(1);
    auto res = std::from_chars(&s.front(), &s.back()+1, value);
    return res.ec!=std::errc{} || res.ptr!=&s.back()+1;
}

//Returns the number converted to double or zero on error.
inline double to_double(std::string_view s) noexcept { double d; return from_chars(s, d) ? 0 : d; }
inline int to_int(std::string_view s) noexcept { int d; return from_chars(s, d) ? 0 : d; }

///////////////////////////////////////////////////////////////////////


template <class Object> class UPtrList;
template <class Object> class SPtrList;

/** A list of object pointers.
 * Used as a container supporting polymorphic objects.
 * We do not own any of our objects, just store a pointer to them.*/
template <class Object>
class NPtrList : public std::list<Object*>
{
public:
    using std::list<Object*>::begin;
    using std::list<Object*>::end;
    using std::list<Object*>::push_back;
    using std::list<Object*>::push_front;
    using std::list<Object*>::splice;
    using std::list<Object*>::clear;

    NPtrList() = default;
    NPtrList(const NPtrList<Object> &o) = default;
    NPtrList(NPtrList<Object> &&o) = default;
    NPtrList & operator = (const NPtrList<Object> &o) = default;
    NPtrList & operator = (NPtrList<Object> &&o) = default;
    NPtrList & operator = (const UPtrList<Object> &o) { clear();  for (auto &p: o) push_back(p.get()); }
    NPtrList & operator = (UPtrList<Object> &&o) = delete; ///<Deleted for we cannot take responsibility for an object
    NPtrList & operator = (const SPtrList<Object> &o) { clear();  for (auto &p: o) push_back(p.get()); }
    NPtrList & operator = (SPtrList<Object> &&o) = delete; ///<Deleted for we cannot take responsibility for an object
    NPtrList *Append(Object *o) {if (o) push_back(o); return this;} ///<Append a pointer to the list
    NPtrList *Append(NPtrList<Object> *l) {if (l) splice(end(), *l); return this;} ///<Append a list to us
    NPtrList *Prepend(Object *o) {if (o) push_front(o); return this;} ///<Prepend a pointer to the list
    NPtrList *Prepend(NPtrList<Object> *l) {if (l) splice(begin(), *l); return this;} ///<Prepend a list to us
    ~NPtrList() = default; ///<No freeing the pointers
};

/** A list of object pointers.
 * Used as a container supporting polymorphic objects.
 * It destroys its objects at deletion.
 * It is almost as cheap as a simple list of pointers, fully owns
 * the objects added to it - and hence does not support copy operations.
 * It comes from "list" so iterators to it remain valid even across manipulations. */
template <class Object>
class UPtrList : public std::list<std::unique_ptr<Object>>
{
public:
    using std::list<std::unique_ptr<Object>>::begin;
    using std::list<std::unique_ptr<Object>>::end;
    using std::list<std::unique_ptr<Object>>::push_back;
    using std::list<std::unique_ptr<Object>>::push_front;
    using std::list<std::unique_ptr<Object>>::splice;
    using std::list<std::unique_ptr<Object>>::clear;
    using std::list<std::unique_ptr<Object>>::erase;

    using std::list<std::unique_ptr<Object>>::list;
    UPtrList() = default;
    UPtrList(const UPtrList<Object> &o) = delete; ///<No copy constructor
    UPtrList(UPtrList<Object> &&o) = default;
    UPtrList & operator = (const UPtrList<Object> &o) = delete; ///<No copy assignment constructor
    UPtrList & operator = (UPtrList<Object> &&o) = default;
    UPtrList *Append(Object *o) { if (o) push_back(std::unique_ptr<Object>(o)); return this; } ///<Append a pointer to the list
    UPtrList *Append(std::unique_ptr<Object> &&o) { if (o) push_back(std::move(o)); return this; } ///<Append a pointer to the list
    UPtrList *Append(std::unique_ptr<UPtrList<Object>> &&l) { if (l) { splice(end(), *l); } return this; } ///<Append a pointer to the list
    UPtrList *Append(UPtrList<Object> *l) { if (l) { splice(end(), *l); } return this; } ///<Append a list to us
    UPtrList &Append(UPtrList<Object> &&l) {splice(end(), l);  return *this; } ///<Append a list to us
    UPtrList *Prepend(Object *o) { if (o) push_front(std::unique_ptr<Object>(o)); return this; } ///<Prepend a pointer to the list
    UPtrList *Prepend(std::unique_ptr<Object> &&o) { if (o) push_front(std::move(o)); return this; } ///<Prepend a pointer to the list
    UPtrList *Prepend(UPtrList<Object> *l) { if (l) { splice(begin(), *l); } return this; } ///<Prepend a list to us
    UPtrList &Prepend(UPtrList<Object> &&l) { splice(begin(), l); return *this; } ///<Prepend a list to us
    void RemoveButKeep(Object *o) { auto i = std::find_if(begin(), end(), [o](const auto &u) {return u.get()==o; }); if (i!=end()) { i->release(); erase(i); } } ///<Remove a pointer from the list, but do not delete it
    void Delete(Object *o) { auto i = std::find_if(begin(), end(), [o](const auto &u) {return u.get()==o; }); if (i!=end()) erase(i);  } ///<Remove a pointer from the list and also delete it
    ~UPtrList() = default;
};

/** Creates a copy of the list by copying all members.
 * Assumes copy constructor for Object.*/
template<class Object>
std::unique_ptr<UPtrList<Object>> Duplicate(const UPtrList<Object> &l)
{
    auto ret = std::make_unique<UPtrList<Object>>();
    for (auto &o : l)
        ret->Append(std::make_unique<Object>(*o));
    return ret;
}


/** A list of object pointers.
 * Used as a container supporting polymorphic objects.
 * It uses shared pointers, so others can point to these objects,
 * It is almost as cheap as a simple list of pointers, fully owns
 * the objects added to it - and hence does not support copy operations.
 * It comes from "list" so iterators to it remain valid even across manipulations. */
template <class Object>
class SPtrList : public std::list<std::shared_ptr<Object>>
{
public:
    using std::list<std::shared_ptr<Object>>::begin;
    using std::list<std::shared_ptr<Object>>::end;
    using std::list<std::shared_ptr<Object>>::push_back;
    using std::list<std::shared_ptr<Object>>::push_front;
    using std::list<std::shared_ptr<Object>>::splice;
    using std::list<std::shared_ptr<Object>>::clear;

    SPtrList() = default;
    SPtrList(const SPtrList<Object> &o) = default;
    SPtrList(SPtrList<Object> &&o) = default;
    SPtrList(UPtrList<Object> &&o) { for (auto &p: o) push_back(std::shared_ptr<Object>(p.release())); o.clear(); } ///<Move a UPtrList to us.
    SPtrList & operator = (const SPtrList<Object> &o) = default;
    SPtrList & operator = (SPtrList<Object> &&o) = default;
    SPtrList & operator = (UPtrList<Object> &&o) { clear();  for (auto &p: o) push_back(std::shared_ptr<Object>(p.release())); o.clear(); } ///<Move a UPtrList to us.
    SPtrList *Append(Object *o) { if (o) push_back(std::shared_ptr<Object>(o)); return this; } ///<Append a pointer to the list
    SPtrList *Append(const std::shared_ptr<Object> &o) { if (o) push_back(o); return this; } ///<Append a pointer to the list
    SPtrList *Append(std::shared_ptr<Object> &&o) { if (o) push_back(std::move(o)); return this; } ///<Append a pointer to the list
    SPtrList *Append(std::unique_ptr<Object> &&o) { if (o) push_back(std::shared_ptr<Object>(o.release())); return this; } ///<Append a pointer to the list
    SPtrList *Append(SPtrList<Object> *l) { if (l) { splice(end(), *l); } return this; } ///<Append (move) a list to us. 'l' remains valid, but empty
    SPtrList *Append(UPtrList<Object> *l) { if (l) { for (auto &p: *l) push_back(std::shared_ptr<Object>(p.release())); l->clear(); } return this; } ///<Append (move) a list to us. 'l' remains valid, but empty
    SPtrList *Prepend(Object *o) { if (o) push_front(std::shared_ptr<Object>(o)); return this; } ///<Prepend a pointer to the list
    SPtrList *Prepend(const std::shared_ptr<Object> &o) { if (o) push_front(o); return this; } ///<Prepend a pointer to the list
    SPtrList *Prepend(std::shared_ptr<Object> &&o) { if (o) push_front(std::move(o)); return this; } ///<Prepend a pointer to the list
    SPtrList *Prepend(std::unique_ptr<Object> &&o) { if (o) push_front(std::shared_ptr<Object>(o.release())); return this; } ///<Prepend a pointer to the list
    SPtrList *Prepend(SPtrList<Object> *l) { if (l) { splice(begin(), *l); } return this; } ///<Prepend a list to us
    void Empty() { clear(); } ///<Empty the list
    ~SPtrList() = default;
};




//Enum helper functions////////////////////////////////////////////

/** A helper class encapsulating enumerations associated with string labels.
 * Used to translate between strings such as "solid" or "dotted" and
 * their respective enumeration value.
 * One has to define the string labels for each enum type as a
 * specialization of the "names" member. There the first value (index of zero)
 * is assumed to be the invalid enumeration (not matched or used), and the array
 * shall end with an empty string.*/
template <typename Enum>
struct EnumEncapsulator {
    const static char names[][ENUM_STRING_LEN];
    const static char * const descriptions[];
};

/** Converts an enum value to its string label*/
template <typename Enum>
string PrintEnum(Enum e) {
    return string(EnumEncapsulator<Enum>::names[int(e)]);
};

/** Converts a string label to its enumerator value (case insensitive).
 * @param [in] name The sting label to convert.
 * @param [out] a The resulting value.
 * @returns False if `name` did not match any label associated with the type of `a`.*/
template <typename Enum>
bool Convert(std::string_view name, Enum &a)
{
    for (unsigned i=1; EnumEncapsulator<Enum>::names[i][0]; i++)
        if (CaseInsensitiveEqual(name, EnumEncapsulator<Enum>::names[i])) {
            a = Enum(i);
            return true;
        }
    return false;
};

/** Returns a slash ('/') separated list of possible values for the type of `dummy1`.*/
template <typename Enum> requires std::is_enum_v<Enum>
string CandidatesFor(const char *separator = "/", Enum from = Enum(1),  Enum till = Enum(INT_MAX)) {
    std::string s;    
    for (std::underlying_type_t<Enum> i = 1; EnumEncapsulator<Enum>::names[i][0]; i++)
        if (std::to_underlying(from) <= i && i <= std::to_underlying(till)) {
            if (s.size()) s.append(separator);
            s.append(EnumEncapsulator<Enum>::names[i]);
        }
    return s;
};

/** Returns a slash ('/') separated list of possible values for the type of `dummy1`.*/
template <typename Enum>
string CandidatesFor(Enum, const char *separator = "/", Enum from = Enum(1), Enum till = Enum(INT_MAX)) { return CandidatesFor<Enum>(separator, from, till); }

////////////////////////////////////////////////////////////

/** An attribute value, which may or may not be set. */
template <typename type>
using OptAttr = std::optional<type>;

/** An attribute value, which may or may not be set, coupled with a file position.
 * It is probably a bad idea to inherit from std::optional, but this way it feels
 * more like any other attribute without having a lot of dispatch methods to an
 * internal std::optional member.*/
template <class type>
struct OptAttrLine : OptAttr<type> {
    FileLineCol file_pos;
    using OptAttr<type>::OptAttr;
    using OptAttr<type>::operator bool;
    constexpr OptAttrLine(const type& y, const FileLineCol& z) : OptAttr<type>(y), file_pos(z) {}
    constexpr OptAttrLine(type&& y, const FileLineCol& z) noexcept(std::is_nothrow_move_constructible_v<type>) : OptAttr<type>(std::move(y)), file_pos(z) {}
};


////////////////////////////////////////////////////////////

/** Describes the type of the value the attribute was assigned.*/
enum class EAttrType {
    CLEAR = 0,  ///<The attribute was assigned no value, like "line.width="
    STRING,     ///<The attribute was assigned a general string
    BOOL,       ///<The attribute was assigned "yes" or "no"
    NUMBER,     ///<The attribute was assigned a (floating-point) number
    STYLE       ///<The construct was not an attribute, but a style specification (no "=" sign, like "strong")
};

/** Describes the context of the use of a style or attribute.*/
enum class EStyleType {
    STYLE,   ///<The attribute is to be applied to a style definition or the style is a user defined style
    DEFAULT, ///<The attribute is to be applied to a default style or the style is a default style such as "box" or "arrow" and MUST be complete.
    DEF_ADD, ///<The style is an additional default style, such as "->" and does not have to be complete.
    ELEMENT, ///<The attribute is to be applied to an element or the style object is part of an element describing it
    OPTION   ///<The attribute is indeed a chart option
};

/** Stores a "<name>=<value>" construct during parsing.
 *
 * This can either be an arc attribute, an entity attribute or a chart option.
 * When parsing attribute lists, we create list of these attributes and
 * pass them to the AddAttributeList() function of ArcBase descendants, EntityApp
 * and Msc (for chart options). In this function, the actual Attribute objects
 * are consumed and deleted*/
class Attribute
{
public:
    EAttrType        type;           ///<The type of value the attribute was assigned
    string           name;           ///<The name of the attribute: the string before the "=" sign
    string           value;          ///<The string representation of the value of the attribute: the string after the "=" sign. Set even if the type is not EAttrType::STRING.
    double           number = 0;     ///<If the type is EAttrType::NUMBER, this contains the value
    bool             yes = false;    ///<If the type is EAttrType::BOOL, this contains the value
    FileLineColRange linenum_attr;   ///<The location of the attribute name in the source file
    FileLineColRange linenum_value;  ///<The location of the attribute value in the source file
    mutable bool     error = false;  ///<True if the attribute specification had already emitted an error for. To prevent reporting an erroneous attribute twice.

    /** Creates a string, number or bool attribute or one with no value (EAttrType::CLEAR)
     * @param [in] a The name of the attribute.
     * @param [in] s The value of the attribute.
     * @param [in] l The location of the name of the attribute.
     * @param [in] v The location of the value of the attribute.
     * @param [in] force_string If true, the attribute will always be created as an EAttrType::STRING.
     *                          If false, we attempt conversion to BOOL ("yes"/"true" or "no"/"false", case insensitive),
     *                          to NUMBER (if a valid base 10 number) or to CLEAR if s is null.*/
    Attribute(std::string_view a, std::string_view s, const FileLineColRange &l = FileLineColRange(),
              const FileLineColRange &v = FileLineColRange(), bool force_string=false);
    /** Creates a style specification */
    Attribute(std::string_view a, const FileLineColRange &l)
        : type(EAttrType::STYLE), name(a)
        , linenum_attr(l), linenum_value(l) {}
    /** Creates a copy of the attribute under a different name */
    Attribute(std::string_view n, const Attribute &a) : Attribute(a) { name = n; }
    Attribute(const Attribute &) = default;
    Attribute(Attribute &&) noexcept = default;
    Attribute(const Attribute &o, const FileLineCol &l) : Attribute(o, l, l.reason) {}
    Attribute(const Attribute &o, const FileLineCol &l, EInclusionReason r);
    Attribute(const Attribute &o, const FileLineColRange &l, EInclusionReason r);

    string Print(int indent=0) const;
    bool Is(std::string_view a) const noexcept
        {return CaseInsensitiveEqual(a, name);}  ///<Returns true if the name of the attribute equals `a` (case insensitive)
    bool EndsWith(std::string_view a) const noexcept;
    bool StartsWith(std::string_view a) const noexcept;
    bool CheckType(EAttrType t, MscError &error) const;
    void InvalidValueError(std::string_view candidates, MscError &error) const;
    void InvalidAttrError(MscError &error) const;
    void InvalidStyleError(MscError &error) const;
    bool EnsureNotClear(MscError &error, EStyleType type) const;
};

/** A list of Attribute object pointers*/
using AttributeList = UPtrList<Attribute>;

class Chart;

////////////////////////////////////////////////////////////

/** Describes the type of a line.*/
enum class ELineType {
    INVALID = 0,  ///<Invalid value
    NONE,         ///<No line shall be drawn
    SOLID,        ///<Continuous, single line
    DOTTED,       ///<Dotted, single line
    DASHED,       ///<Dashed, single line
    LONG_DASHED,  ///<Single line with long dashes
    DASH_DOT,     ///<Dash-dot single line
    DOUBLE,       ///<Continuous double line
    TRIPLE,       ///<Continuous triple line
    TRIPLE_THICK  ///<Continuous triple line, with the middle line being thicker
};

/** True if the line type is continuous (single, double or triple).*/
constexpr bool IsLineTypeContinuous(ELineType t) noexcept {return t!=ELineType::DOTTED && t!=ELineType::DASHED && t!=ELineType::LONG_DASHED && t!=ELineType::DASH_DOT;}
/** True if the line is of double type*/
constexpr bool IsLineTypeDouble(ELineType t) noexcept {return t==ELineType::DOUBLE;}
/** True if the line is of triple type*/
constexpr bool IsLineTypeTriple(ELineType t) noexcept {return t==ELineType::TRIPLE || t==ELineType::TRIPLE_THICK;}
/** True if the line is either of double or triple type*/
constexpr bool IsLineTypeDoubleOrTriple(ELineType t) noexcept {return IsLineTypeDouble(t) || IsLineTypeTriple(t);}
/** Returns how thick is the line if line width is 1. E.g., a double line is 3x thicker, two for the two lines and one for the space in between.*/
constexpr double LineWidthMultiplier(ELineType t) noexcept {return t==ELineType::NONE ? 0 : t==ELineType::DOUBLE ? 3 : t==ELineType::TRIPLE ? 5 : t==ELineType::TRIPLE_THICK ? 6 : 1;}

/** Describes the possible rectangle corner types*/
enum ECornerType {
    INVALID = 0, ///<The invalid value
    NONE,        ///<The basic rectangular corner type
    ROUND,       ///<The corner is rounded
    BEVEL,       ///<The corner is chopped
    NOTE         ///<Only the right-upper corner is special: a small note flip
};

/** Returns by how much the corner radius is to be increased if the rectangle is expanded by 1 pixel.*/
constexpr double RadiusIncMultiplier(ECornerType t) noexcept {return t==ECornerType::ROUND ? 1 : t==ECornerType::BEVEL || t==ECornerType::NOTE ? 0.58578643762 : 0;} //this is 1-tan(22.5 deg)

/** Stores the properties of a line (including rectangle corner style).*/
class LineAttr {
protected:
    Contour CreateRectangle_ForFill_Note(double x1, double x2, double y1, double y2) const;
    Contour CreateRectangle_InnerEdge_Note(double x1, double x2, double y1, double y2) const;
public:
    OptAttr<ELineType> type = ELineType::SOLID; ///<The style of the line.
    OptAttr<ColorType> color = ColorType{0,0,0};///<The color of the line.
    OptAttr<double>    width = 1;               ///<The width of the line.
    ///<The radius of the corners.
    ///<Ignored if `corner` is ECornerType::NONE. It is also used for pipes to set the size of the oval
    ///<and also for SelfArrow to set the radius of the arrow.
    OptAttr<double>      radius = 0;            ///<The radius associated with this line. Used by self-pointing arrows, box corners, vertical brackets and braces, etc.
    OptAttr<ECornerType> corner = ECornerType::NONE;///<The style of the corners. Also used with bent arrows.
    constexpr LineAttr() noexcept = default; ///<Create a fully specified default line style. Solid line, black color, width of 1, zero radius and no corner.
    constexpr explicit LineAttr(ELineType t) noexcept : type{t}, color{}, width{}, radius{}, corner{} {} ///<Creates an empty style, where only the type is set to `t`.
    constexpr explicit LineAttr(ColorType c) noexcept : type{}, color{c}, width{}, radius{}, corner{} {} ///<Creates an empty style, where only the color is set to `c`.
    constexpr explicit LineAttr(double w) noexcept : type{}, color{}, width{w}, radius{}, corner{} {}    ///<Creates an empty style, where only the linewidth is set to `w`.
    constexpr LineAttr(ELineType t, ColorType c, double w=1., ECornerType ct=ECornerType::ROUND, double r=-1) noexcept :
        type(t), color(c), width(w), radius(r), corner(ct) {} ///<Creates a fully specified line style with the attributes given.
    void Empty() noexcept { type.reset(); color.reset(); width.reset(); corner.reset(); radius.reset(); } ///<Clear all content from the line style.
    constexpr bool IsEmpty() const noexcept {return !type && !color && !width && !corner && !radius;} ///<False if any of the line attributes are set.
    constexpr bool IsComplete() const noexcept {return type && color && width && corner && radius;} ///<True if all of the line attributes are set.
    void MakeComplete() noexcept;
    LineAttr &operator +=(const LineAttr&a) noexcept; ///<Applies `a` to us: sets all our attributes, which are set in `a` to the value in `a`; leaves the rest unchanged.
    constexpr bool operator == (const LineAttr &a) const noexcept = default;
    constexpr bool operator < (const LineAttr& a) const noexcept = default;
    static constexpr LineAttr None() noexcept { return LineAttr(ELineType::NONE, ColorType::none()); }

    //functions about line style
    bool IsFullyTransparent() const noexcept { _ASSERT(type && color);  return *type<=ELineType::NONE || color->IsFullyTransparent(); } ///<True if no line will be drawn
    bool IsContinuous() const noexcept {_ASSERT(type); return IsLineTypeContinuous(*type);} ///<True if the line style is continuous.
    bool IsDouble() const noexcept {_ASSERT(type); return IsLineTypeDouble(*type);} ///<True if the line style is double.
    bool IsTriple() const noexcept {_ASSERT(type); return IsLineTypeTriple(*type);} ///<True if the line style is triple.
    bool IsDoubleOrTriple() const noexcept {return IsDouble() || IsTriple();} ///<True if the line style is double or triple.
    /** Returns how much the midline of the outer lines are spaced from the midline of the total line.
     * For single lines return 0. For doubles it returns the width. For triple it returns 2x the width, for triple thick, it returns 2.5 x the width.*/
    double Spacing() const noexcept {_ASSERT(width); return *width * (IsDouble() ? 1 : IsTriple() ? (*type == ELineType::TRIPLE_THICK)?2.5:2.0 : 0);}
    /** Assuming a triple line, how wide is the middle line.*/
    double TripleMiddleWidth() const noexcept {_ASSERT(IsTriple() && width); return *width*((*type == ELineType::TRIPLE_THICK)?2.0:1.0);}
    /** Returns the total line width.*/
    double LineWidth() const noexcept {_ASSERT(type && width); return *width * LineWidthMultiplier(*type);}
    //double RadiusIncMul() const {_ASSERT(type); return ::RadiusIncMultiplier(corner.value);}
    /** Updates the `radius` if a corresponding rectangle is expanded by `gap`.*/
    LineAttr& Expand(double gap) noexcept {_ASSERT(IsComplete()); if (*radius>0 && *corner!=ECornerType::NONE) radius = std::max(0., *radius + gap*::RadiusIncMultiplier(*corner)); return *this;}
    /** If given the midline of a box by (x1,x2,y1,y2), what is the maximum radius for our line width and style.*/
    double MaxRadius(double x1, double x2, double y1, double y2) const noexcept {return MaxRadius(x1-x2, y1-y2);}
    /** If given the midline width and height of a box by (x,y), what is the maximum radius for our line width and style.*/
    double MaxRadius(double x, double y) const noexcept { return std::max(0., (std::min(fabs(x), fabs(y))-LineWidth())/2); }
    /** If given the midline of a box by (x1,x2,y1,y2), sanitize our `radius`.*/
    double SaneRadius(double x1, double x2, double y1, double y2) const noexcept {return std::max(0., std::min(*radius, MaxRadius(x1, x2, y1, y2)));}
    /** If given the midline of a box by `b`, sanitize our `radius`.*/
    double SaneRadius(const Block &b) const noexcept {return SaneRadius(b.x.from, b.x.till, b.y.from, b.y.till);}
    constexpr static unsigned MAX_PATTERN_LEN = 6;
    /** Return the dash pattern to be used for this line style. `num` returns the length of the pattern.*/
    unsigned DashPattern(std::span<double> pattern) const noexcept;

    virtual bool AddAttribute(const Attribute &a, Chart *chart, EStyleType t);
    static void AttributeNames(Csh &csh, std::string_view prefix);
    static bool AttributeValues(std::string_view attr, Csh &csh);
    string Print(int indent = 0) const;

    /** @name Rectangle shape creators.
     * The below 12 functions creates various rectangle contours using the line style
     * corner style, line width and radius of us. Four functions, each having three variants in how
     * the input rectangle is specified (four coordinates, two XY structs or a Block).
     * In each case the input rectangle specifies the midline of the final rectangle, that is
     * the middle of the line around the rectangle. The line then lies halfway outside and
     * halfway inside around this contour. Similar, the radius attribute of us is understood
     * as the radius at the midline (the outer edge will have a somewhat larger radius, whereas
     * the inner edge a somewhat smaller one).*/
    /** @{ */

    /** Creates the midline of the rectangle according to corner style.
     * This one does not assume anything about whether the resulting rectangle should be the
     * outer edge or inner edge of the line - just uses the radius value and coordinates
     * as they are. (So if 'b' is the middle of the line, the result will also be at the
     * middle of the line.) Here we simply add round and bevel corners if applicable.
     * For ECornerType::NOTE it creates the outer line only.
     * You can specify a radius different from the one stored in us. Leaving that out
     * defaults to the radius stored in `this`*/
    Contour CreateRectangle_Midline(double x1, double x2, double y1, double y2, double r = -1) const;
    /** Creates the midline of the rectangle according to corner style.
     * This one does not assume anything about whether the resulting rectangle should be the
     * outer edge or inner edge of the line - just uses the radius value and coordinates
     * as they are. (So if 'b' is the middle of the line, the result will also be at the
     * middle of the line.) Here we simply add round and bevel corners if applicable.
     * For ECornerType::NOTE it creates the outer line only.*/
    Contour CreateRectangle_Midline(const XY &s, const XY &d) const {return CreateRectangle_Midline(s.x, d.x, s.y, d.y);}
    /** Creates the midline of the rectangle according to corner style.
     * This one does not assume anything about whether the resulting rectangle should be the
     * outer edge or inner edge of the line - just uses the radius value and coordinates
     * as they are. (So if 'b' is the middle of the line, the result will also be at the
     * middle of the line.) Here we simply add round and bevel corners if applicable.
     * For ECornerType::NOTE it creates the outer line only.*/
    Contour CreateRectangle_Midline(const Block &b) const {return CreateRectangle_Midline(b.x.from, b.x.till, b.y.from, b.y.till);}

    /** Creates a contour representing the outer edge of the rectangle.
     * It assumes x1, x2, y1, y2 are at the middle of the line.
     * This one considers linewidth and returns the outer edge of the line.
     * For ECornerType::NOTE it creates the outer line only.*/
    Contour CreateRectangle_OuterEdge(double x1, double x2, double y1, double y2) const {const double lw2 = LineWidth()/2; return CreateRectangle_Midline(x1-lw2, x2+lw2, y1-lw2, y2+lw2, *radius+lw2*::RadiusIncMultiplier(*corner));}
    /** Creates a contour representing the outer edge of the rectangle.
     * It assumes s and d are at the middle of the line.
     * This one considers linewidth and returns the outer edge of the line.
     * For ECornerType::NOTE it creates the outer line only.*/
    Contour CreateRectangle_OuterEdge(const XY &s, const XY &d) const {const double lw2 = LineWidth()/2; return CreateRectangle_Midline(s.x-lw2, d.x+lw2, s.y-lw2, d.y+lw2, *radius+lw2*::RadiusIncMultiplier(*corner));}
    /** Creates a contour representing the outer edge of the rectangle.
     * It assumes b is at the middle of the line.
     * This one considers linewidth and returns the outer edge of the line.
     * For ECornerType::NOTE it creates the outer line only.*/
    Contour CreateRectangle_OuterEdge(const Block &b) const {const double lw2 = LineWidth()/2; return CreateRectangle_Midline(b.x.from-lw2, b.x.till+lw2, b.y.from-lw2, b.y.till+lw2, *radius+lw2*::RadiusIncMultiplier(*corner));}

    /** Returns the area to fill for a rectangle.
     * It assumes x1, x2, y1, y2 are at the middle of the line.
     * This one considers linewidth and returns the middle of the inner line (for double or triple)
     * (or the middle of the line for single).
     * For ECornerType::NOTE it creates the inner line and the triangle
     * (ret[0] will be the body and ret[1] will be the triangle).*/
    Contour CreateRectangle_ForFill(double x1, double x2, double y1, double y2) const;
    /** Returns the area to fill for a rectangle.
     * It assumes s and d are at the middle of the line.
     * This one considers linewidth and returns the middle of the inner line (for double or triple)
     * (or the middle of the line for single).
     * For ECornerType::NOTE it creates the inner line and the triangle
     * (ret[0] will be the body and ret[1] will be the triangle).*/
    Contour CreateRectangle_ForFill(const XY &s, const XY &d) const {return CreateRectangle_ForFill(s.x, d.x, s.y, d.y);}
    /** Returns the area to fill for a rectangle.
     * It assumes b is at the middle of the line.
     * This one considers linewidth and returns the middle of the inner line (for double or triple)
     * (or the middle of the line for single).
     * For ECornerType::NOTE it creates the inner line and the triangle
     * (ret[0] will be the body and ret[1] will be the triangle).*/
    Contour CreateRectangle_ForFill(const Block &b) const {return CreateRectangle_ForFill(b.x.from, b.x.till, b.y.from, b.y.till);}

    /** Returns the inner edge of the line around a rectangle.
     * This one considers linewidth and returns the inner edge of the inner line
     * For ECornerType::NOTE it creates the inner line only.*/
    Contour CreateRectangle_InnerEdge(double x1, double x2, double y1, double y2) const;
    /** Returns the inner edge of the line around a rectangle.
     * This one considers linewidth and returns the inner edge of the inner line
     * For ECornerType::NOTE it creates the inner line only.*/
    Contour CreateRectangle_InnerEdge(const XY &s, const XY &d) const {return CreateRectangle_InnerEdge(s.x, d.x, s.y, d.y);}
    /** Returns the inner edge of the line around a rectangle.
     * This one considers linewidth and returns the inner edge of the inner line
     * For ECornerType::NOTE it creates the inner line only.*/
    Contour CreateRectangle_InnerEdge(const Block &b) const {return CreateRectangle_InnerEdge(b.x.from, b.x.till, b.y.from, b.y.till);}
    /** @} */

    DoublePair CalculateTextMargin(Contour textCover, double rect_top) const;
};

/** Describes gradient fill direction for the user*/
enum class EGradientType {
    INVALID = 0,  ///<The invalid value
    NONE, ///<No gradient in fill - single color only
    OUTWARD,  ///<Radial gradient outwards from center
    INWARD,   ///<Radial gradient inwards toward center
    DOWN, ///<Linear gradient downwards
    UP,   ///<Linear gradient upwards
    LEFT, ///<Linear gradient leftwards
    RIGHT,///<Linear gradient rightwards
    BUTTON///<Linear gradient with multiple changes creating a button effect
};

/** An internal enumeration listing all possible fill types, even the ones not
 * possible to specify via fill.gradient (such as DEGENERATE).*/
enum class EIntGradType
{
    INVALID = 0,///<The invalid value
    NONE,       ///<No gradient in fill - single color only
    DEGENERATE, ///<No gradient in fill - but use two colors (linear fashion)
    LINEAR,     ///<Linear gradient
    BUTTON,     ///<Linear gradient with multiple changes creating a button effect
    RADIAL,     ///<Radial gradient outwards from center
};



/** Stores the properties of a fill (color and gradient).*/
struct FillAttr {
public:
    static const double gradient_angl[];    ///<The default gradient angle for each EGradientType value
    static const EIntGradType gradient_tr[]; ///<The default gradient type for each EGradientType value
    OptAttr<ColorType> color;        ///<The color of the fill.
    OptAttr<ColorType> color2;       ///<The secondary color of the fill if we use a gradient.
    OptAttr<double> gradient_angle;  ///<The gradient angle of the fill. For radiant gradients, if negative we do 'in'.
    OptAttr<EIntGradType> gradient;  ///<The type of gradient to use
    OptAttr<double> frac;            ///<[0..1] the fraction of the first color from the gradient
    constexpr FillAttr() noexcept : color(ColorType(255,255,255)), color2(ColorType(255,255,255)), gradient_angle(-1), gradient(EIntGradType::NONE), frac(0.5) {}
    constexpr explicit FillAttr(ColorType c) noexcept : color(c) {} ///<Creates an empty style, where only the color is set to `c`.
    constexpr explicit FillAttr(ColorType c, EGradientType g) noexcept :
        color(c), gradient_angle(gradient_angl[(unsigned)g]), gradient(gradient_tr[(unsigned)g]), frac(0.5) {} ///<Creates a fully specified fill style with the attributes given.
    constexpr explicit FillAttr(ColorType c, ColorType c2) noexcept :
        color(c), color2(c2), gradient_angle(0), gradient(EIntGradType::NONE), frac(0.5) {} ///<Creates an incomplete fill style with two colors given.
    constexpr explicit FillAttr(ColorType c, ColorType c2, EGradientType g) noexcept :
        color(c), color2(c2), gradient_angle(gradient_angl[(unsigned)g]), gradient(gradient_tr[(unsigned)g]), frac(0.5) {} ///<Creates a fully specified fill style with the attributes given.
    void Empty() noexcept {color.reset(); color2.reset(); gradient_angle.reset(); gradient.reset(); frac.reset();} ///<Clear all content from the fill style.
    void MakeComplete() noexcept;
    constexpr bool IsFullyTransparent() const noexcept { _ASSERT(IsComplete());  return color->IsFullyTransparent() && ((color2 && color2->IsFullyTransparent()) || *gradient<=EIntGradType::NONE); } ///<True if no fill will be drawn
    constexpr bool IsFullyOpaque() const noexcept { _ASSERT(IsComplete());  return color->IsFullyOpaque() && ((color2 && color2->IsFullyOpaque()) || *gradient<=EIntGradType::NONE); } ///<True if no fill will be drawn
    constexpr bool HasRealGradient() const noexcept { _ASSERT(IsComplete()); return *gradient>EIntGradType::DEGENERATE; } ///<Returns true if the fill has non-degenerate gradient coloring
    constexpr bool IsEmpty() const noexcept { return !color && !color2 && !gradient_angle && !gradient && !frac; } ///<False if any of the line attributes are set.
    constexpr bool IsComplete() const noexcept {return color && gradient_angle && gradient && frac;} ///<True if all of the line attributes are set. (But color2 is not needed.)
    FillAttr &operator +=(const FillAttr&a) noexcept; ///<Applies `a` to us: sets all our attributes, which are set in `a` to the value in `a`; leaves the rest unchanged.
    constexpr bool operator == (const FillAttr& a) const noexcept = default;
    static constexpr FillAttr None() noexcept { return FillAttr(ColorType::none(), EGradientType::NONE); } ///<Creates no fill, fully specified
    static constexpr FillAttr Solid(ColorType c) noexcept { return FillAttr(c, EGradientType::NONE); }     ///<Creates a solid color fill, fully specified

    virtual bool AddAttribute(const Attribute &a, Chart*chart, EStyleType t);
    static void AttributeNames(Csh &csh, std::string_view prefix);
    static bool AttributeValues(std::string_view attr, Csh &csh);
    void FlipGradient(bool updown) noexcept;
    void RotateGradient(double deg) noexcept;
    string Print(int indent = 0) const;
    FillAttr ApproxColorHorizontal(double pos) const noexcept;
};

/** Stores the properties of shadows (color, offset and blur depth).*/
struct ShadowAttr {
public:
    OptAttr<ColorType> color = ColorType::black();   ///<The color of the shadow at its darkest - can be somewhat transparent.
    OptAttr<double> offset = 0; ///<The offset of the shadow from the object, indicating how much the object is above the surface.
    OptAttr<double> blur = 0;   ///<Indicating how many pixels are blurred at the edge of the shadow.
    /** Create a fully specified default shadow style.
     * Black color, no offset (effectively no shadow) and no blur.*/
    constexpr ShadowAttr() noexcept = default;
    constexpr explicit ShadowAttr(ColorType c) noexcept : color{c}, offset{}, blur{} {} ///<Creates an empty style, where only the color is set to `c`.
    constexpr bool IsNone() const noexcept { _ASSERT(IsComplete()); return !color || color->IsFullyTransparent() || offset==0; }
    void Empty() noexcept { color.reset();  offset.reset();  blur.reset(); } ///<Clear all content from the shadow style.
    bool IsEmpty() const noexcept { return !color && !offset && !blur; }  ///<Returns true if none of the elements are set
    void MakeComplete() noexcept;
    constexpr bool IsComplete() const noexcept {return color && offset && blur;} ///<True if all of the shadow attributes are set.
    ShadowAttr &operator +=(const ShadowAttr&a) noexcept; ///<Applies `a` to us: sets all our attributes, which are set in `a` to the value in `a`; leaves the rest unchanged.
    constexpr bool operator == (const ShadowAttr &a) const noexcept = default;
    static constexpr ShadowAttr None() noexcept { return ShadowAttr(); }

    virtual bool AddAttribute(const Attribute &a, Chart*chart, EStyleType t);
    static void AttributeNames(Csh &csh);
    static bool AttributeValues(std::string_view attr, Csh &csh);
    string Print(int indent = 0) const;
};


/** Describes arrowhead types. Both for line and block arrows.
 * The following definitions are used here.
 * - a single arrowhead type is when a single visual element is used.
 *   E.g., 'double' or 'triple_half' are not single.
 * - an arrowhead type is symmetric, when it is displayed centered on the
 *   target, like a dot or jumpover.
 * - the 'target' of an arrow is the entity line or object contour it points
 *   to. Some arrowheads actually cover it to some extent.
 * - some arrow types are applicable only for block arrows, line arrows,
 *   some are applicable for both.
 * - an arrowhead is empty, if it is an enclosed area, with only the contour
 *   drawn with lines. In these cases we want the background to be visible in
 *   the interior, so we need to skip drawing the arrow line or the target
 *   in these places. So we clip them away.*/
enum class EArrowType {
    //Both big and small arrows
    INVALID = 0,  ///<Invalid value
    NONE,         ///<No arrowhead
    SOLID,        ///<Line: filled triangle, Block: wider than the body
    EMPTY,        ///<Line: empty triangle, Block: as wide as body
    LINE,         ///<Line: just two lines
    HALF,    ///<Line: just one line
    DIAMOND,      ///<A filled diamond shape
    DIAMOND_EMPTY,///<An empty diamond shape
    DOT,          ///<A filled circle
    DOT_EMPTY,    ///<An empty circle
    SHARP,        ///<A filled triangle with miters
    SHARP_EMPTY,  ///<An empty triangle with miters
    //Line arrows only
    VEE,          ///<Like a '<' sign (from graphviz)
    VEE_EMPTY,    ///<Like a '<' sign, but empty inside (from graphviz)
    NSDIAMOND,    ///<A filled diamond shape, that is not symmetric
    NSDIAMOND_EMPTY,///<An empty diamond shape, that is not symmetric
    NSDOT,        ///<A filled circle, that is not symmetric
    NSDOT_EMPTY,  ///<An empty circle, that is not symmetric
    JUMPOVER,     ///<Line only: jump over line: used for skip
    DOUBLE,       ///<Line only: Two solid triangles
    DOUBLE_EMPTY, ///<Line only: Two empty triangles
    DOUBLE_LINE,  ///<Line only: Two set of lines
    DOUBLE_HALF,  ///<Line only: Two single lines
    TRIPLE,       ///<Line only: Three solid triangles
    TRIPLE_EMPTY, ///<Line only: Three empty triangles
    TRIPLE_LINE,  ///<Line only: Three set of lines
    TRIPLE_HALF,  ///<Line only: Three single lines
    //Block arrows only
    EMPTY_INV,    ///<Block only: Inward triangle as wide as body
    STRIPES,      ///<Block only: Three rectangles of increasing size
    TRIANGLE_STRIPES ///<Block only: Three triangles of increasing size
};

/** True if the type is applicable for line arrows */
constexpr bool GoodForArrows(EArrowType t) noexcept { return t>EArrowType::INVALID && t<=EArrowType::TRIPLE_HALF; }
/** True if the type identifies a single element (and is good for arrows). */
constexpr bool IsSingle(EArrowType t) noexcept { return t>EArrowType::NONE && t<=EArrowType::JUMPOVER; }
/** True if the type identifies a single element or none (and is good for arrows). */
constexpr bool IsSingleOrNone(EArrowType t) noexcept { return t>=EArrowType::NONE && t<=EArrowType::JUMPOVER; }
/** True if type is of double (and is good for arrows). */
constexpr bool IsDouble(EArrowType t) noexcept {return t>=EArrowType::DOUBLE && t<=EArrowType::DOUBLE_HALF;}
/** True if type is of triple (and is good for arrows). */
constexpr bool IsTriple(EArrowType t) noexcept {return t>=EArrowType::TRIPLE && t<=EArrowType::TRIPLE_HALF;}
/** Returns 2 for double, 3 for triple and 1 for single types.*/
constexpr unsigned Duplicity(EArrowType t) noexcept { return IsTriple(t) ? 3 : IsDouble(t) ? 2 : 1; }
/** Returns the simple variant of double and triple arrows or the argument if not such.*/
constexpr EArrowType SingleVariant(EArrowType t) noexcept { return IsTriple(t) ? EArrowType(unsigned(t)+unsigned(EArrowType::SOLID)-unsigned(EArrowType::TRIPLE)) : IsDouble(t) ? EArrowType(unsigned(t)+unsigned(EArrowType::SOLID)-unsigned(EArrowType::DOUBLE)) : t; }
/** True if type is a single line sort (and is good for arrows)*/
constexpr bool IsHalf(EArrowType t) noexcept {return t==EArrowType::HALF || t==EArrowType::DOUBLE_HALF || t==EArrowType::TRIPLE_HALF;}
/** True if type is line-like (and is good for arrows).*/
constexpr bool IsLine(EArrowType t) noexcept {return t==EArrowType::LINE || t==EArrowType::DOUBLE_LINE || t==EArrowType::TRIPLE_LINE;}
/** True if the arrowhead is of shape of an actual triangle (and good for arrows). Used to determine spacing for 'line' type of triangles. E.g., >> can be laid out closer. */
constexpr bool IsTriangleShape(EArrowType t) noexcept { return (EArrowType::SOLID<=t && t<=EArrowType::HALF) || (EArrowType::DOUBLE<=t && t<=EArrowType::TRIPLE_HALF); }
/** True if the type has a hollow center (and is good for arrows)*/
constexpr bool IsEmpty(EArrowType t) noexcept { return t==EArrowType::EMPTY || t==EArrowType::DIAMOND_EMPTY || t==EArrowType::DOT_EMPTY || t==EArrowType::NSDIAMOND_EMPTY || t==EArrowType::NSDOT_EMPTY || t==EArrowType::SHARP_EMPTY || t==EArrowType::VEE_EMPTY || t==EArrowType::DOUBLE_EMPTY || t==EArrowType::TRIPLE_EMPTY; }
/** True if the type is filled but have an empty variant (and is good for arrows)*/
constexpr bool HasEmptyVariant(EArrowType t) noexcept { return t==EArrowType::SOLID || t==EArrowType::DIAMOND || t==EArrowType::NSDIAMOND || t==EArrowType::DOT || t==EArrowType::NSDOT || t==EArrowType::SHARP || t==EArrowType::VEE || t==EArrowType::DOUBLE || t==EArrowType::TRIPLE; }
/** Returns the empty variant of an arrowhead (if exists) (only for ones good for an arrow)*/
constexpr EArrowType EmptyVariant(EArrowType t) noexcept { return HasEmptyVariant(t) ? EArrowType(1+(unsigned)t) : t; }
/** True if the type is applicable for block arrows */
constexpr bool GoodForBlockArrows(EArrowType t) noexcept {return t>EArrowType::INVALID && (t<=EArrowType::SHARP_EMPTY || t>=EArrowType::EMPTY_INV);}
/** True if the type symmetric to a horizontal line, but is not 'none'. */
constexpr bool IsSymmetricAndNotNone(EArrowType t) noexcept { return (EArrowType::DIAMOND<=t && t<=EArrowType::DOT_EMPTY) || t==EArrowType::JUMPOVER; }
/** True if the type symmetric to a horizontal line. */
constexpr bool IsSymmetricOrNone(EArrowType t) noexcept { return t==EArrowType::NONE || IsSymmetricAndNotNone(t); }

/** Describes the size of the arrowhead, see FullArrowAttr::arrowSizePercentage*/
enum class EArrowSize {
    INVALID = 0, ///<The invalid value. Used as 5% size for hint popup pictograms
    EPSILON,     ///<10% size
    SPOT,        ///<20% size
    TINY,        ///<30% size
    SMALL,       ///<50% size
    NORMAL,      ///<75% size
    BIG,         ///<100% size
    ENORMOUS     ///<150% size
};

static constexpr double _arrowScale[8]      ///<Scale values for EArrowSize enum, for line arrows
= {0.05, 0.10, 0.20, 0.30, 0.50, 0.75, 1., 1.30}; //INVALID, EPSILON, SPOT, TINY, SMALL, NORMAL, BIG, ENORMOUS

static constexpr double _blockScale[8]      ///<Scale values for EArrowSize enum, for block arrows
= {0.04, 0.08, 0.12, 0.20, 0.40, 0.60, 1.00, 1.40};

/** Returns scaling factor for a given arrow size (relative to NORMAL)*/
constexpr double ArrowScale(EArrowSize s) noexcept { return _arrowScale[unsigned(s)]; }

/** Returns scaling factor for a given arrow size (relative to NORMAL)*/
constexpr double BlockArrowScale(EArrowSize s) noexcept { return _blockScale[unsigned(s)]; }

/** Describes the overall type of an arrow */
enum class EArcArrowType
{
    NONE,     ///<Invalid
    ARROW,    ///<Line arrow
    BIGARROW, ///<Block arrow
    ANY      ///<Can be either type
};

/** Returns if this arrowhead type 't' is applicable for this kind of arrow class 'at'.*/
constexpr bool GoodForThisArrowType(EArcArrowType at, EArrowType t) noexcept { return at==EArcArrowType::ARROW ? GoodForArrows(t) : at==EArcArrowType::BIGARROW ? GoodForBlockArrows(t) : at==EArcArrowType::ANY; }

/** Describes the position of the arrowhead in the arrow.*/
enum class EArrowEnd
{
    END,   ///<The arrowhead in the 'to' direction, e.g., in case of 'a->b', the arrowhead at 'b'. Both ends are like this for bidirectional arrows.
    START,  ///<The arrowhead in the 'from' direction, e.g., in case of 'a->b', the arrowhead at 'a'.
    MIDDLE,///<An arrowhead at the middle of a multi-segment arrow, e.g., for 'a->b->c' the one at 'b'.
    SKIP,  ///<An arrowhead at entities we jump over arrow, e.g., for 'a->c' the one at 'b' (if we have three entities: a, b and c).
};

/** Returns true if this position is in the middle of the arrow.*/
constexpr bool IsMiddle(EArrowEnd e) noexcept { return e==EArrowEnd::MIDDLE || e==EArrowEnd::SKIP; }


bool CshHintGraphicCallbackForYesNo(Canvas *canvas, CshHintGraphicParam p, CshHintStore &);
bool CshHintGraphicCallbackForArrows(Canvas *canvas, EArrowType type, EArrowSize size, bool left);
bool CshHintGraphicCallbackForBigArrows(Canvas *canvas, CshHintGraphicParam p, CshHintStore&);
bool CshHintGraphicCallbackForBigArrowsDetailed(Canvas *canvas, EArrowType type, const XY &tip, double width = HINT_GRAPHIC_SIZE_Y*0.4,
                                                int dir = 0, ColorType color = ColorType(0, 192, 32), const XY &size = {0.1, 0.1},
                                                const LineAttr &eLine = LineAttr(ELineType::SOLID, ColorType()));

class ArrowHead;

/** Stores attributes for arrows having an arrowhead only on one end.
 * It can apply both to line and block arrows.
 * You can customize the attribute name it takes for style.*/
class OneArrowAttr
{
protected:
    std::pair<OptAttr<EArrowType>*, OptAttr<std::string>*> GetArrowAttr(EArrowEnd which)
    { _ASSERT(which==EArrowEnd::END); if (which==EArrowEnd::END) return{&endType, &gvEndType}; return{nullptr, nullptr}; }
    std::pair<const OptAttr<EArrowType>*, const OptAttr<std::string>*> GetArrowAttr(EArrowEnd which) const
    { _ASSERT(which==EArrowEnd::END); if (which==EArrowEnd::END) return{&endType, &gvEndType}; return{nullptr, nullptr}; }
    bool SetArrowType(const Attribute &a, Chart *chart, EStyleType t, OptAttr<EArrowType> &attr);
    bool SetArrowType(const Attribute &a, Chart *chart, EStyleType t, OptAttr<std::string> &gvattr);
    void ApplyArrowType(OptAttr<EArrowType> &my_type, const OptAttr<EArrowType> &other_type,
                        OptAttr<std::string> &my_gvtype, const OptAttr<std::string> &other_gvtype,
                        EArcArrowType other_t);
    void CreateAnArrowHead(ArrowHead &ah, double scale,
                           const OptAttr<EArrowType>& oldType, const OptAttr<std::string>& gvType) const;
    mutable bool        dirty = true; ///<Later on we add ArrowHead objects. This signals if they need to be re-generated.
    EArcArrowType       type;     ///<What kind of arrows do we apply to
    LineAttr            line;     ///<The line style for the arrowhead
    OptAttr<EArrowSize> size;     ///<The size of the arrowhead. Defaults to SMALL.
    OptAttr<double>     xmul;     ///<The x-size multiplier of the arrowhead. Defaults to 1.
    OptAttr<double>     ymul;     ///<The y-size multiplier of the arrowhead. Defaults to 1.
    OptAttr<EArrowType> endType;  ///<The type of the arrowhead at the end of the arrow (also start for bidirectional arrows).
    OptAttr<std::string>gvEndType;///<The type of the arrowhead expressed as a gv style string

public:
    std::string name;             ///<Our name. Usually 'end' or "". Recognized in attribute names such as xxx.endtype and xxx.gvendtype.
    bool accepts_arrow_attr;      ///<Whether we accept 'arrow' and 'arrowsize' attributes (with a deprecated warning) or not.
    /** Creates a default arrowhead style.
     * The style will accept any type (both line or block arrow types),
     * small size, no size multipliers, solid triangle type at the end
     * no arrowhead at the start and default line style (black, solid, width of 1).
     * We allow specifying the name of this arrow end. It defaults to nothing, which
     * means it only listens to 'xxx.type' and 'arrow' (the latter only if 'aa' is set).*/
    explicit OneArrowAttr(EArcArrowType t = EArcArrowType::ANY, std::string_view n = {}, bool aa = false)
        : type(t), size(EArrowSize::SMALL), xmul(1), ymul(1),
        endType(EArrowType::SOLID), name(n), accepts_arrow_attr(aa) {}
    bool operator ==(const OneArrowAttr &a) const { return type==a.type && line==a.line && size==a.size && xmul==a.xmul && ymul==a.ymul && endType == a.endType && gvEndType == a.gvEndType; }
    EArcArrowType GetArcArrowType() const { return type; }
    /** Read the arrow type attribute. Returns null, if we do not have that kind of arrow end. */
    const OptAttr<EArrowType> *GetType(EArrowEnd which) const { return GetArrowAttr(which).first; }
    /** Read the graphviz arrow type attribute. Returns null, if we do not have that kind of arrow end. */
    const OptAttr<std::string> *GetGvType(EArrowEnd which) const { return GetArrowAttr(which).second; }
    /** Read the arrow type attribute (and resets the grapviz one if t is true). Returns false, if we do not have that kind of arrow end. */
    bool SetType(EArrowEnd which, const OptAttr<EArrowType>& t) { auto a = GetArrowAttr(which); if (a.first) *a.first = t; if (a.second && t) a.second->reset(); dirty = true;  return a.first!=nullptr; }
    /** Read the graphviz arrow type attribute (and resets the old one if t is true). Returns false, if we do not have that kind of arrow end. */
    bool SetGvType(EArrowEnd which, const OptAttr<std::string> &t) { auto a = GetArrowAttr(which); if (a.second) *a.second = t; if (a.first && t) a.first->reset(); dirty = true; return a.second!=nullptr; }
    /** Returns our line attribute */
    const LineAttr &GetLine() const { return line; }
    /** Sets the line attribute (color of which is the color of the arrowheads). Sets our dirty bit.*/
    void SetLine(const LineAttr &l) { line = l; dirty = true; }
    /** Sets the line color (which is the color of the arrowheads). Sets our dirty bit.*/
    void SetLineColor(ColorType c) { line.color = c; dirty = true; }
    const OptAttr<EArrowSize> &GetSize() const { return size; }
    void SetSize(EArrowSize s) { size = s; dirty = true; }
    /** Deletes all content, clears all attributes. */
    void Empty();
    /** Returns true if no attribute is set. */
    bool IsEmpty() const noexcept { return line.IsEmpty() && !size && !endType && !gvEndType && !xmul && !ymul; }
    /** True if all attributes are set*/
    bool IsComplete() const { return line.IsComplete() && size && (endType || gvEndType) && xmul && ymul; }
    /** Set attributes that are not yet set (use default values)*/
    void MakeComplete();
    /** Merge another arrowhead style to us by copying any attribute that is set there.*/
    OneArrowAttr & operator += (const OneArrowAttr &);
    bool AddAttribute(const Attribute &a, Chart* chart, EStyleType t);
    /** Add a list of attribute names we accept to `csh`.*/
    static void AttributeNames(Csh &csh, std::string_view prefix);
    /** Add a list of attribute values we accept for attribute `attr` to `csh`.*/
    static bool AttributeValues(std::string_view attr, Csh &csh, EArcArrowType t);
    /** Return the scale for us considering 'size' and 'xmul' and 'ymul'.*/
    XY GetScale() const { _ASSERT(IsComplete());  return ArrowScale(*size)*XY(*xmul, *ymul); }
};

/** Stores attributes of arrowheads.
 * This attribute-set contains 2 arrowhead types, one for the start and end of the arrow.
 * You can set only one arrowhead segment (which may be double/triple though) and can
 * have uniform size and line/color for all the four types.
 * It can apply both to line and block arrows.*/
class TwoArrowAttr : public OneArrowAttr
{
protected:
    std::pair<OptAttr<EArrowType>*, OptAttr<std::string>*> GetArrowAttr(EArrowEnd which)
    { _ASSERT(which<=EArrowEnd::START); if (which==EArrowEnd::START) return{&startType, &gvStartType}; return OneArrowAttr::GetArrowAttr(which); }
    std::pair<const OptAttr<EArrowType>*, const OptAttr<std::string>*> GetArrowAttr(EArrowEnd which) const
    { _ASSERT(which<=EArrowEnd::START); if (which==EArrowEnd::START) return{&startType, &gvStartType}; return OneArrowAttr::GetArrowAttr(which); }
    OptAttr<EArrowType> startType;  ///<The type of the arrowhead at the start of the arrow.
    OptAttr<std::string>gvStartType;///<The type of the arrowhead expressed as a gv style string

public:
    /** Creates a default arrowhead style.
     * The style will accept any type (both line or block arrow types),
     * small size, no size multipliers, solid triangle type at the end
     * no arrowhead at the start and default line style
     * (black, solid, width of 1).*/
    explicit TwoArrowAttr(EArcArrowType t = EArcArrowType::ANY) : OneArrowAttr(t, "end", true), startType(EArrowType::NONE) {}
    bool operator ==(const TwoArrowAttr &a) const { return OneArrowAttr::operator==(a) && startType==a.startType && gvStartType==a.gvStartType; }
    /** Read the arrow type attribute. Returns null, if we do not have that kind of arrow end. */
    const OptAttr<EArrowType> *GetType(EArrowEnd which) const { return GetArrowAttr(which).first; }
    /** Read the graphviz arrow type attribute. Returns null, if we do not have that kind of arrow end. */
    const OptAttr<std::string> *GetGvType(EArrowEnd which) const { return GetArrowAttr(which).second; }
    /** Read the arrow type attribute (and resets the grapviz one if t is true). Returns false, if we do not have that kind of arrow end. */
    bool SetType(EArrowEnd which, const OptAttr<EArrowType> &t) { auto a = GetArrowAttr(which); if (a.first) *a.first = t; if (a.second && t) a.second->reset(); dirty = true;  return a.first!=nullptr; }
    /** Read the graphviz arrow type attribute (and resets the old one if t is true). Returns false, if we do not have that kind of arrow end. */
    bool SetGvType(EArrowEnd which, const OptAttr<std::string> &t) { auto a = GetArrowAttr(which); if (a.second) *a.second = t; if (a.first && t) a.first->reset(); dirty = true; return a.second!=nullptr; }
    /** Deletes all content, by clearing all attributes. */
    void Empty();
    /** Returns true if no attribute is set. */
    bool IsEmpty() const noexcept { return !startType && !gvStartType && OneArrowAttr::IsEmpty(); }
    /** True if all attributes are set*/
    bool IsComplete() const { return (startType || gvStartType) && OneArrowAttr::IsComplete(); }
    /** Set attributes that are not yet set (use default values)*/
    void MakeComplete();
    /** Merge another arrowhead style to us by copying any attribute that is set there.*/
    TwoArrowAttr & operator += (const TwoArrowAttr &);
    bool AddAttribute(const Attribute &a, Chart* chart, EStyleType t);
    /** Add a list of attribute names we accept to `csh`.*/
    static void AttributeNames(Csh &csh, std::string_view prefix);
    /** Add a list of attribute values we accept for attribute `attr` to `csh`.*/
    static bool AttributeValues(std::string_view attr, Csh &csh, EArcArrowType t);
};

/** Stores attributes of arrowheads.
 * This attribute-set contains 4 arrowhead types, one for the start and end of the arrow,
 * and two for internal stops: one where the arrow actually stop (mid) and where the
 * arrow passes by an entity jumping it over (skip).
 * You can set only one arrowhead segment (which may be double/triple though) and can
 * have uniform size and line/color for all the four types.
 * It can apply both to line and block arrows.*/
class FullArrowAttr : public TwoArrowAttr
{
protected:
    std::pair<OptAttr<EArrowType>*, OptAttr<std::string>*> GetArrowAttr(EArrowEnd which)
    { if (which==EArrowEnd::SKIP) return{&skipType, &gvSkipType}; if (which==EArrowEnd::MIDDLE) return{&midType, &gvMidType}; return TwoArrowAttr::GetArrowAttr(which); }
    std::pair<const OptAttr<EArrowType>*, const OptAttr<std::string>*> GetArrowAttr(EArrowEnd which) const
    { if (which==EArrowEnd::SKIP) return{&skipType, &gvSkipType}; if (which==EArrowEnd::MIDDLE) return{&midType, &gvMidType}; return TwoArrowAttr::GetArrowAttr(which); }
    OptAttr<EArrowType> midType;  ///<The type of the arrowhead at the midpoint of a multi-segment of the arrow.
    OptAttr<EArrowType> skipType; ///<The type of the arrowhead at entities jumped over by the arrow.
    OptAttr<std::string>gvMidType;///<The type of the arrowhead expressed as a gv style string
    OptAttr<std::string>gvSkipType;///<The type of the arrowhead expressed as a gv style string

public:
    /** Creates a default arrowhead style.
     * The style will accept any type (both line or block arrow types),
     * small size, no size multipliers, solid triangle type, both at the end
     * and in the middle, no arrowhead at the start and default line style
     * (black, solid, width of 1).*/
    explicit FullArrowAttr(EArcArrowType t = EArcArrowType::ANY) : TwoArrowAttr(t),
        midType(EArrowType::SOLID), skipType(EArrowType::NONE) {}
    bool operator ==(const FullArrowAttr &a) const { return TwoArrowAttr::operator==(a) && midType==a.midType && skipType==a.skipType &&gvMidType==a.gvMidType && gvSkipType==a.gvSkipType; }

    /** Read the arrow type attribute. Returns null, if we do not have that kind of arrow end. */
    const OptAttr<EArrowType> *GetType(EArrowEnd which) const { return GetArrowAttr(which).first; }
    /** Read the graphviz arrow type attribute. Returns null, if we do not have that kind of arrow end. */
    const OptAttr<std::string> *GetGvType(EArrowEnd which) const { return GetArrowAttr(which).second; }
    /** Read the arrow type attribute (and resets the grapviz one if t is true). Returns false, if we do not have that kind of arrow end. */
    bool SetType(EArrowEnd which, const OptAttr<EArrowType> &t) { auto a = GetArrowAttr(which); if (a.first) *a.first = t; if (a.second && t) a.second->reset(); dirty = true;  return a.first!=nullptr; }
    /** Read the graphviz arrow type attribute (and resets the old one if t is true). Returns false, if we do not have that kind of arrow end. */
    bool SetGvType(EArrowEnd which, const OptAttr<std::string> &t) { auto a = GetArrowAttr(which); if (a.second) *a.second = t; if (a.first && t) a.first->reset(); dirty = true; return a.second!=nullptr; }
    void Empty();
    /** Returns true if no attribute is set. */
    bool IsEmpty() const noexcept { return !midType && !gvMidType && ((!skipType && !gvSkipType) || type == EArcArrowType::BIGARROW) && TwoArrowAttr::IsEmpty(); }
    bool IsComplete() const { return (midType || gvMidType) && (skipType || gvSkipType || type==EArcArrowType::BIGARROW) && TwoArrowAttr::IsComplete(); }
    void MakeComplete();
    FullArrowAttr & operator += (const FullArrowAttr &);
    bool AddAttribute(const Attribute &a, Chart* chart, EStyleType t);
    static void AttributeNames(Csh &csh, std::string_view prefix);
    static bool AttributeValues(std::string_view attr, Csh &csh, EArcArrowType t);
};

class StringFormat;

/** Stores the value of the width attribute - a number or a string */
class WidthAttr
{
    bool is_set;
    mutable double value;
    std::string str;
public:
    WidthAttr() noexcept { Empty(); MakeComplete(); }
    void Empty() noexcept { is_set = false; }
    bool IsEmpty() const noexcept { return !is_set; }
    void MakeComplete() noexcept { if (!is_set) { is_set = true; value = 0; str.clear(); } }
    WidthAttr &operator +=(const WidthAttr&a) { if (a.is_set) *this = a; return *this; }///<Applies `a` to us: sets all our attributes, which are set in `a` to the value in `a`; leaves the rest unchanged.
    bool operator == (const WidthAttr &a) const noexcept;
    bool AddAttribute(const Attribute &a, Chart* chart, EStyleType t);
    static bool AttributeValues(std::string_view attr, Csh &csh);
    double GetWidth(Canvas &canvas, const ShapeCollection &shapes, const StringFormat &format) const;
};


#endif
