/*
    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_context.cpp The declaration of basic context related classes, procedures and inclusion support.
 * @ingroup libcgencommon_files */


#if !defined(CONTEXT_H)
#define CONTEXT_H

#include <cstring>
#include <string>

namespace gsl {
//
// owner<T> is designed as a bridge for code that must deal directly with owning pointers for some reason
//
// T must be a pointer type
// - disallow construction from any type other than pointer type
//
template <class T, class = std::enable_if_t<std::is_pointer<T>::value>>
using owner = T;

}

struct svhash {
    using is_transparent = void;
    auto operator()(std::string_view s) const { return std::hash<std::string_view>{}(s); }
    auto operator()(const std::string &s) const { return std::hash<std::string>{}(s); }
};

/** Enumerates ways to create a context */
enum class EContextCreate
{
    EMPTY, ///<Create an empty context
    PLAIN, ///<Create a context set to the 'plain' design
    CLEAR, ///<Initialize only base members to sane (empty) values. No default colors or styles to be added.
};

/** Enumerates parsing behaviour for a given context*/
enum class EContextParse
{
    NORMAL,      ///<We normally parse the context. This is how we parse main text.
    REPARSING,   ///<We are currently replaying a stored procedure.
    SKIP_CONTENT ///<In this context should skip what we read. We do not check style/color/procedure/design/marker/shape names and also ignore any elements generated. We also block design/shape definitions as they are global and procedure definitions as well. Such input text is either conditional and we dont need it (ifthenelse) or will later be reparsed (e.g., proc defs.).
};

class ContextParams
{
protected:
    bool is_full;                   ///<True if the context contains a setting for all chart options and a complete value for all default styles.
    const EContextParse parse_mode; ///<Determines, what to observe when parsing input text in this context.
    ContextParams(bool f, EContextParse p) : is_full(f), parse_mode(p), starts_procedure(false) {}
    ContextParams(const ContextParams &o) = default;
    ContextParams(ContextParams &&o) = default;
public:
    /** True, if this context is the top-level content of a procedure (either
     * recording or replaying). Used to delineate procedure parameters.
     * That is, any parameter name shall only be searched up to where it was defined.
     * In other words, no parameter of a procedure shall be visible in a called procedure.*/
    bool starts_procedure;
    virtual ~ContextParams() = default;
    bool IsFull() const { return is_full; }
    bool Reparsing() const { return parse_mode==EContextParse::REPARSING; }
    bool SkipContent() const { return parse_mode==EContextParse::SKIP_CONTENT; }
    EContextParse GetParseMode() const { return parse_mode; }
};


/** Enumerates string comparison operators*/
enum class ECompareOperator
{
    INVALID = 0,
    SMALLER,            ///< '<'
    SMALLER_OR_EQUAL,   ///< '<='
    EQUAL,
    NOT_EQUAL,
    GREATER_OR_EQUAL,   ///< '=>'
    GREATER,            ///< '>'
};
struct str_view {
    const char* data;
    size_t len;
    str_view() noexcept = default;
    str_view(std::string_view a) noexcept : data(a.data()), len(a.size()) {}
    void init() noexcept { data = nullptr; len = 0; }
    void set(std::string_view a) { data = a.data(); len = a.size(); }
    std::string_view view() const noexcept { return {data, len}; }
    std::string str() const { return {data, len}; }
    operator std::string_view() const noexcept { return std::string_view{data, len}; }
    char operator[](size_t pos) const noexcept { return data[pos]; }
    explicit operator bool() const noexcept { return bool(len); }
    bool empty() const noexcept { return len == 0; }
};

/** A class holding a string during parsing that possibly comes from concatenation operator (~)
 * It must be a POD as it will be part of bison's union.
 * Each segment can be a string, a quoted string or a parameter - all need different coloring.
 * The issues is that by the time of collecting these parts we do not know if they will be a label or
 * attribute name or entity name, etc. So in order to color them correctly each segment shall be added
 * to Csh::MultiElements by Csh::StoreMulti(). Then AddCSH will apply either the given color or
 * COLOR_PARAMNAME if the segment was a param. Likewise, AddCSH_AttrValue_CheckAndAddEscapeHint() will
 * also apply text formatting segment by segment.
 * Note that these two functions remove the elements they have processed from MultiElements, so this
 * array is supposed to be very short all the time. To ensure removal of stale elements, we call
 * Csh::ClearMulti() from Csh::AddInstruction(), as we expect that by then we have colored all multi
 * strings collected and we can forget about parts. */
class multi_segment_string
{
    const char *data;       ///<the string owned/referenced by this structure not null terminated
    uint32_t len;
    uint32_t owning : 1;    ///<Invariant: We are never owning if len==0

    //combined length of the arguments or -1 if any is a multi_segment_string with had_error=true
    template <typename A, typename ...B>
    static int length_(A&& a, B&&...b) noexcept {
        using AA = std::decay<A>::type;
        int l = 0;
        if constexpr (sizeof...(b)) {
            l = length_(b...);
            if (l < 0) return l;
        }
        if constexpr (std::is_same_v<AA, char>)
            return 1 + l;
        else if constexpr (std::is_same_v<AA, str_view>)
            return a.len + l;
        else if constexpr (std::is_same_v<AA, multi_segment_string>)
            return a.had_error ? -1 : a.len + l;
        else if constexpr (std::is_same_v<AA, std::string_view> || std::is_same_v<AA, std::string>)
            return a.size() + l;
        else if constexpr (std::is_same_v<AA, const char*>)
            return strlen(a) + l;
        else
            static_assert(std::is_same_v<AA, str_view> || std::is_same_v<AA, multi_segment_string> || std::is_same_v<AA, std::string_view> || std::is_same_v<AA, std::string> || std::is_same_v<AA, const char*>);
        return -1;
    }
    //concat the contents and |= had_params.
    template <typename A, typename ...B>
    void copy_(char* p, A&& a, B&&...b) noexcept {
        using AA = std::decay<A>::type;
        if constexpr (std::is_same_v<AA, char>) {
            *p = a;
            p++;
        } else if constexpr (std::is_same_v<AA, str_view>) {
            std::memcpy(p, a.data, a.len);
            p += a.len;
        } else if constexpr (std::is_same_v<AA, multi_segment_string>) {
            if (a.len) {
                std::memcpy(p, a.data, a.len);
                p += a.len;
            }
            had_param |= a.had_param;
        } else if constexpr (std::is_same_v<AA, std::string_view> || std::is_same_v<AA, std::string>) {
            std::memcpy(p, a.data(), a.length());
            p += a.length();
        } else if constexpr (std::is_same_v<AA, const char*>) {
            std::memcpy(p, a, strlen(a));
            p += strlen(a);
        } else
            static_assert(std::is_same_v<AA, str_view> || std::is_same_v<AA, multi_segment_string> || std::is_same_v<AA, std::string_view> || std::is_same_v<AA, std::string> || std::is_same_v<AA, const char*>);
        if constexpr (sizeof...(b))
            copy_(p, b...);
    }
    template <typename A, typename ...B>
    static void destroy_(A&& a, B&&...b) noexcept {
        using AA = std::decay<A>::type;
        if constexpr (std::is_same_v<AA, multi_segment_string>)
            a.destroy();
        if constexpr (sizeof...(b))
            destroy_(b...);
    }
public:
    uint32_t multi : 1;      ///<True if the string actually had a concatenation operator
    uint32_t had_param : 1;  ///<True if the string had a parameter in it
    uint32_t had_error : 1;  ///<True if there was an error (+sing at end, bad parameter name, etc.)

    bool empty() const noexcept { return len == 0; }
    std::string_view view() const noexcept { return {data, len}; }
    std::string str() const { return {data, len}; }
    operator std::string_view() const noexcept { return view(); }
    /** Assumes uninitialized */
    void init() noexcept { data = nullptr; len = 0; owning = multi = had_param = had_error = false; }
    /** Assumes uninitialized */
    void set(str_view b) noexcept { init();  data = b.data; len = b.len; }
    /** Assumes uninitialized */
    void set_owning(std::string &&b) noexcept {
        init();
        if (b.empty()) return;
        if (char* p = (char*)malloc(b.size())) {
            data = p;
            len = b.size();
            std::memcpy(p, b.data(), len);
            owning = true;
        }
    }
    /** Assumes uninitialized */
    void set_error() noexcept { init(); had_error = true; }
    /** Assumes uninitialized, makes a and b invalid (not even destroy() shall be called)
     * Optimized version. If one of the elements is empty no allocation is made. */
    template <typename A, typename B>
        requires ((std::is_same_v<A, str_view> || std::is_same_v<A, multi_segment_string>)
               && (std::is_same_v<B, str_view> || std::is_same_v<B, multi_segment_string>))
        void CombineThemToMe(A& a, B& b) noexcept {
        init();
        multi = true;
        if constexpr (std::is_same_v<A, multi_segment_string>) {
            had_param |= a.had_param;
            had_error |= a.had_error;
        }
        if constexpr (std::is_same_v<B, multi_segment_string>) {
            had_param |= b.had_param;
            had_error |= b.had_error;
        }
        if (had_error) {
            if constexpr (std::is_same_v<A, multi_segment_string>) a.destroy();
            if constexpr (std::is_same_v<B, multi_segment_string>) b.destroy();
        } else if (a.len == 0) {
            data = b.data;
            len = b.len;
            if constexpr (std::is_same_v<B, multi_segment_string>) owning = b.owning;
        } else if (b.len == 0) {
            data = a.data;
            len = a.len;
            if constexpr (std::is_same_v<A, multi_segment_string>) owning = a.owning;
        } else {
            char* p = (char*)malloc(len = a.len + b.len);
            std::memcpy(p, a.data, a.len);
            std::memcpy(p + a.len, b.data, b.len);
            data = p;
            owning = true;
            if constexpr (std::is_same_v<A, multi_segment_string>) a.destroy();
            if constexpr (std::is_same_v<B, multi_segment_string>) b.destroy();
        }
    }
    /** Assumes uninitialized, makes a and b invalid (not even destroy() shall be called)
     * This takes arbitrary many elements, but always allocates except on error.*/
    template <typename A, typename ...B>
    void CombineThemToMe(A&& a, B&&...b) noexcept {
        init();
        multi = sizeof...(b)>0;
        const int len_ = length_(a, b...);
        had_error = len_ < 0;
        if (len_>0) { //if len==0, do nothing
            char* p = (char*)malloc(len_);
            len = len_;
            data = p;
            copy_(p, a, b...);
            owning = true;
        }
        destroy_(a, b...);
    }
    /** Assumes initialized */
    void destroy() noexcept { if (owning) free(const_cast<char*>(data)); init(); }
    /** Assumes initialized, results in non-owning (needs no destruction after) */
    gsl::owner<char*> extract_owning() noexcept {
        if (had_error) { return nullptr; }
        else if (owning) { owning = false; return const_cast<char*>(data); }
        else if (gsl::owner<char*> p = (char*)malloc(len+1)) {
            std::memcpy(p, data, len);
            p[len] = 0;
            return p;
        }
        return nullptr;
    }
    /** Return 1 if the two strings fulfill the 'op' and 0 if not.
     * Return 2 if any parameter is in error.*/
    int Compare(ECompareOperator op, const multi_segment_string& o) const noexcept
    {
        if (had_error || o.had_error) return 2;
        const auto res = view() <=> o.view();
        switch (op) {
        default:
        case ECompareOperator::INVALID:          return 2;
        case ECompareOperator::SMALLER:          return res < 0;
        case ECompareOperator::SMALLER_OR_EQUAL: return res <= 0;
        case ECompareOperator::EQUAL:            return res == 0;
        case ECompareOperator::NOT_EQUAL:        return res != 0;
        case ECompareOperator::GREATER_OR_EQUAL: return res >= 0;
        case ECompareOperator::GREATER:          return res > 0;
        }
    }
};

#endif //CONTEXT_H

