// See the file "COPYING" in the main distribution directory for copyright.

#pragma once

#include <climits>
#include <string>

namespace zeek {

class ODesc;

namespace detail {

class Location final {
public:
    constexpr Location(const char* fname, int line_f, int line_l) noexcept : filename(fname) {
        SetLines(line_f, line_l);
    }

    Location() = default;

    void Describe(ODesc* d) const;

    bool operator==(const Location& l) const;
    bool operator!=(const Location& l) const { return ! (*this == l); }

    const char* FileName() const { return filename; }
    int FirstLine() const { return first_line; }
    int LastLine() const { return last_line; }

    void SetFile(const char* fname) { filename = fname; }
    void SetLine(int line) { SetLines(line, line); }
    constexpr void SetLines(int first, int last) {
        if ( first > last ) {
            // We don't use std::swap() here because it's not
            // constexpr-enabled on older versions of C++.
            auto tmp = first;
            first = last;
            last = tmp;
        }
        first_line = first;
        last_line = last;
    }
    void SetFirstLine(int line) { SetLines(line, last_line); }
    void SetLastLine(int line) { SetLines(first_line, line); }
    void IncrementLine(int incr = 1) {
        first_line += incr;
        last_line += incr;
    }

private:
    const char* filename = nullptr;
    int first_line = 0, last_line = 0;
};

#define YYLTYPE zeek::detail::yyltype
using yyltype = Location;
YYLTYPE GetCurrentLocation();
void SetCurrentLocation(YYLTYPE currloc);

// Used to mean "no location associated with this object".
inline constexpr Location no_location("<no location>", 0, 0);

// Current start/end location.
extern Location start_location;
extern Location end_location;

// Used by parser to set the above.
inline void set_location(const Location loc) { start_location = end_location = loc; }

inline void set_location(const Location start, const Location end) {
    start_location = start;
    end_location = end;
}

// Helper for updating e's location to the one used by o, returning e.
template<typename T, typename U>
T with_location_of(T e, const U& o) {
    e->SetLocationInfo(o->GetLocationInfo());
    return e;
}

} // namespace detail

class Obj {
public:
    Obj() {
        // A bit of a hack.  We'd like to associate location
        // information with every object created when parsing,
        // since for them, the location is generally well-defined.
        // We could maintain a separate flag that tells us whether
        // we're inside a parse, but the parser also sets the
        // location to no_location when it's done, so it makes
        // sense to just check for that.  *However*, start_location
        // and end_location are maintained as their own objects
        // rather than pointers or references, so we can't directly
        // check them for equality with no_location.  So instead
        // we check for whether start_location has a line number
        // of 0, which should only happen if it's been assigned
        // to no_location (or hasn't been initialized at all).
        location = nullptr;
        if ( detail::start_location.FirstLine() != 0 )
            SetLocationInfo(&detail::start_location, &detail::end_location);
    }

    virtual ~Obj();

    /* disallow copying */
    Obj(const Obj&) = delete;
    Obj& operator=(const Obj&) = delete;

    // Report user warnings/errors.  If obj2 is given, then it's
    // included in the message, though if pinpoint_only is non-zero,
    // then obj2 is only used to pinpoint the location.
    void Warn(const char* msg, const Obj* obj2 = nullptr, bool pinpoint_only = false,
              const detail::Location* expr_location = nullptr) const;
    void Error(const char* msg, const Obj* obj2 = nullptr, bool pinpoint_only = false,
               const detail::Location* expr_location = nullptr) const;

    // Report internal errors.
    [[noreturn]] void BadTag(const char* msg, const char* t1 = nullptr, const char* t2 = nullptr) const;

    // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CHECK_TAG(t1, t2, text, tag_to_text_func)                                                                      \
    {                                                                                                                  \
        if ( (t1) != (t2) )                                                                                            \
            BadTag(text, tag_to_text_func(t1), tag_to_text_func(t2));                                                  \
    }

    [[noreturn]] void Internal(const char* msg) const;
    void InternalWarning(const char* msg) const;

    virtual void Describe(ODesc* d) const { /* FIXME: Add code */ };

    void AddLocation(ODesc* d) const;

    // Get location info for debugging.
    virtual const detail::Location* GetLocationInfo() const { return location ? location : &detail::no_location; }

    virtual bool SetLocationInfo(const detail::Location* loc) { return SetLocationInfo(loc, loc); }

    // Location = range from start to end.
    virtual bool SetLocationInfo(const detail::Location* start, const detail::Location* end);

    // Set new end-of-location information.  This is used to
    // extend compound objects such as statement lists.
    virtual void UpdateLocationEndInfo(const detail::Location& end);

    // Enable notification of plugins when this objects gets destroyed.
    void NotifyPluginsOnDtor() { notify_plugins = true; }

    int RefCnt() const { return ref_cnt; }

    // Helper class to temporarily suppress errors
    // as long as there exist any instances.
    class SuppressErrors {
    public:
        SuppressErrors() { ++Obj::suppress_errors; }
        ~SuppressErrors() { --Obj::suppress_errors; }
    };

    void Print() const;

protected:
    detail::Location* location; // all that matters in real estate

private:
    friend class SuppressErrors;

    void DoMsg(ODesc* d, const char s1[], const Obj* obj2 = nullptr, bool pinpoint_only = false,
               const detail::Location* expr_location = nullptr) const;
    void PinPoint(ODesc* d, const Obj* obj2 = nullptr, bool pinpoint_only = false) const;

    friend inline void Ref(Obj* o);
    friend inline void Unref(Obj* o);

    int ref_cnt = 1;
    bool notify_plugins = false;

    // If non-zero, do not print runtime errors.  Useful for
    // speculative evaluation.
    static int suppress_errors;
};

// Sometimes useful when dealing with Obj subclasses that have their
// own (protected) versions of Error.
inline void Error(const Obj* o, const char* msg) { o->Error(msg); }

[[noreturn]] extern void bad_ref(int type);

inline void Ref(Obj* o) {
    if ( ++(o->ref_cnt) <= 1 )
        bad_ref(0);
    if ( o->ref_cnt == INT_MAX )
        bad_ref(1);
}

inline void Unref(Obj* o) {
    if ( o && --o->ref_cnt <= 0 ) {
        if ( o->ref_cnt < 0 )
            bad_ref(2);
        delete o;

        // We could do the following if o were passed by reference.
        // o = (Obj*) 0xcd;
    }
}

// A dict_delete_func that knows to Unref() dictionary entries.
extern void obj_delete_func(void* v);

} // namespace zeek
