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

#pragma once

#include "zeek/zeek-config.h"

#include <cstdarg>
#include <exception>
#include <list>
#include <map>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>

#include "zeek/ZeekList.h"
#include "zeek/net_util.h"

namespace zeek {

class Connection;
class EventHandlerPtr;
class RecordVal;
class StringVal;
class IPAddr;
class Reporter;

template<class T>
class IntrusivePtr;
using RecordValPtr = IntrusivePtr<RecordVal>;
using StringValPtr = IntrusivePtr<StringVal>;

namespace detail {

class AssertStmt;
class Expr;
class Frame;
class Location;

} // namespace detail

namespace analyzer {
class Analyzer;
}
namespace file_analysis {
class File;
}

// One cannot raise this exception directly, go through the
// Reporter's methods instead.

class ReporterException : public std::exception {
protected:
    friend class Reporter;
    ReporterException() = default;
};

class InterpreterException : public ReporterException {
protected:
    friend class Reporter;
    friend class detail::AssertStmt;
    InterpreterException() = default;
};

#define FMT_ATTR __attribute__((format(printf, 2, 3))) // sic! 1st is "this" I guess.

class Reporter {
public:
    using IPPair = std::pair<IPAddr, IPAddr>;
    using ConnTuple = std::tuple<IPAddr, IPAddr, uint32_t, uint32_t, TransportProto>;
    using WeirdCountMap = std::unordered_map<std::string, uint64_t>;
    using WeirdFlowMap = std::map<IPPair, WeirdCountMap>;
    using WeirdConnTupleMap = std::map<ConnTuple, WeirdCountMap>;
    using WeirdSet = std::unordered_set<std::string>;

    Reporter(bool abort_on_scripting_errors);
    ~Reporter();

    // Initialize reporter-specific options	that are defined in script-layer.
    void InitOptions();

    // Report an informational message, nothing that needs specific
    // attention.
    void Info(const char* fmt, ...) FMT_ATTR;

    // Report a warning that may indicate a problem.
    void Warning(const char* fmt, ...) FMT_ATTR;

    // Report a non-fatal error. Processing proceeds normally after the error
    // has been reported.
    void Error(const char* fmt, ...) FMT_ATTR;

    // Returns the number of errors reported so far.
    int Errors() { return errors; }

    // Report a fatal error. Zeek will terminate after the message has been
    // reported.
    [[noreturn]] void FatalError(const char* fmt, ...) FMT_ATTR;

    // Report a fatal error. Zeek will terminate after the message has been
    // reported and always generate a core dump.
    [[noreturn]] void FatalErrorWithCore(const char* fmt, ...) FMT_ATTR;

    // Report a runtime error in evaluating a Zeek script expression. This
    // function will not return but raise an InterpreterException.
    [[noreturn]] void ExprRuntimeError(const detail::Expr* expr, const char* fmt, ...)
        __attribute__((format(printf, 3, 4)));

    // Report a runtime error in evaluating a Zeek script expression. This
    // function will not return but raise an InterpreterException.
    [[noreturn]] void RuntimeError(const detail::Location* location, const char* fmt, ...)
        __attribute__((format(printf, 3, 4)));

    // Report a rutnime warning in evaluating a Zeek script expression.
    void ExprRuntimeWarning(const detail::Expr* expr, const char* fmt, ...) __attribute__((format(printf, 3, 4)));

    // Report a runtime error in executing a compiled script. This
    // function will not return but raise an InterpreterException.
    [[noreturn]] void CPPRuntimeError(const char* fmt, ...) __attribute__((format(printf, 2, 3)));

    // Similar, but for warnings. This function does return.
    void CPPRuntimeWarning(const char* fmt, ...) __attribute__((format(printf, 2, 3)));

    // Report a traffic weirdness, i.e., an unexpected protocol situation
    // that may lead to incorrectly processing a connection.
    void Weird(const char* name, const char* addl = "",
               const char* source = ""); // Raises net_weird().
    void Weird(file_analysis::File* f, const char* name, const char* addl = "",
               const char* source = ""); // Raises file_weird().
    void Weird(Connection* conn, const char* name, const char* addl = "",
               const char* source = ""); // Raises conn_weird().
    void Weird(RecordValPtr conn_id, StringValPtr uid, const char* name, const char* addl = "",
               const char* source = ""); // Raises expired_conn_weird().
    void Weird(const IPAddr& orig, const IPAddr& resp, const char* name, const char* addl = "",
               const char* source = ""); // Raises flow_weird().

    // Report a deprecation. The message should contain a version.
    void Deprecation(std::string_view msg, const detail::Location* loc1 = nullptr,
                     const detail::Location* loc2 = nullptr);

    // Whether or not deprecations are logged when calling Deprecation()
    void SetIgnoreDeprecations(bool arg) { ignore_deprecations = arg; }

    // Syslog a message. This methods does nothing if we're running
    // offline from a trace.
    void Syslog(const char* fmt, ...) FMT_ATTR;

    // Report about a potential internal problem. Zeek will continue
    // normally.
    void InternalWarning(const char* fmt, ...) FMT_ATTR;

    // Report an internal program error. Zeek will terminate with a core
    // dump after the message has been reported.
    [[noreturn]] void InternalError(const char* fmt, ...) FMT_ATTR;

    // Report an analyzer error. That analyzer will be set to not process
    // any further input, but Zeek otherwise continues normally.
    void AnalyzerError(analyzer::Analyzer* a, const char* fmt, ...) __attribute__((format(printf, 3, 4)));

    // Toggle whether non-fatal messages should be reported through the
    // scripting layer rather on standard output. Fatal errors are always
    // reported via stderr.
    void ReportViaEvents(bool arg_via_events) { via_events = arg_via_events; }

    // Associates the given location with subsequent output. We create a
    // stack of location so that the most recent is always the one that
    // will be assumed to be the current one. The pointer must remain
    // valid until the location is popped.
    void PushLocation(const detail::Location* location) { locations.emplace_back(location, nullptr); }

    void PushLocation(const detail::Location* loc1, const detail::Location* loc2) {
        locations.emplace_back(loc1, loc2);
    }

    // Removes the top-most location information from stack.
    void PopLocation() { locations.pop_back(); }

    // Signals that we're entering processing an error handler event.
    void BeginErrorHandler() { ++in_error_handler; }

    // Signals that we're done processing an error handler event.
    void EndErrorHandler() { --in_error_handler; }

    /**
     * Reset/cleanup state tracking for a "net" weird.
     */
    void ResetNetWeird(const std::string& name);

    /**
     * Reset/cleanup state tracking for a "flow" weird.
     */
    void ResetFlowWeird(const IPAddr& orig, const IPAddr& resp);

    /**
     * Reset/cleanup state tracking for a "expired conn" weird.
     */
    void ResetExpiredConnWeird(const ConnTuple& id);

    /**
     * Return the total number of weirds generated (counts weirds before
     * any rate-limiting occurs).
     */
    uint64_t GetWeirdCount() const { return weird_count; }

    /**
     * Return number of weirds generated per weird type/name (counts weirds
     * before any rate-limiting occurs).
     */
    const WeirdCountMap& GetWeirdsByType() const { return weird_count_by_type; }

    /**
     * Gets the weird sampling whitelist.
     */
    const WeirdSet& GetWeirdSamplingWhitelist() const { return weird_sampling_whitelist; }

    /**
     * Sets the weird sampling whitelist.
     *
     * @param weird_sampling_whitelist New weird sampling whitelist.
     */
    void SetWeirdSamplingWhitelist(WeirdSet weird_sampling_whitelist) {
        this->weird_sampling_whitelist = std::move(weird_sampling_whitelist);
    }

    /**
     * Gets the weird sampling global list.
     */
    const WeirdSet& GetWeirdSamplingGlobalList() const { return weird_sampling_global_list; }

    /**
     * Sets the weird sampling global list.
     *
     * @param weird_sampling_global list New weird sampling global list.
     */
    void SetWeirdSamplingGlobalList(WeirdSet weird_sampling_global_list) {
        this->weird_sampling_global_list = std::move(weird_sampling_global_list);
    }

    /**
     * Gets the current weird sampling threshold.
     *
     * @return weird sampling threshold.
     */
    uint64_t GetWeirdSamplingThreshold() const { return weird_sampling_threshold; }

    /**
     * Sets the current weird sampling threshold.
     *
     * @param weird_sampling_threshold New weird sampling threshold.
     */
    void SetWeirdSamplingThreshold(uint64_t weird_sampling_threshold) {
        this->weird_sampling_threshold = weird_sampling_threshold;
    }

    /**
     * Gets the current weird sampling rate.
     *
     * @return weird sampling rate.
     */
    uint64_t GetWeirdSamplingRate() const { return weird_sampling_rate; }

    /**
     * Sets the weird sampling rate.
     *
     * @param weird_sampling_rate New weird sampling rate.
     */
    void SetWeirdSamplingRate(uint64_t weird_sampling_rate) { this->weird_sampling_rate = weird_sampling_rate; }

    /**
     * Gets the current weird sampling duration.
     *
     * @return weird sampling duration.
     */
    double GetWeirdSamplingDuration() const { return weird_sampling_duration; }

    /**
     * Sets the current weird sampling duration. Please note that
     * this will not delete already running timers.
     *
     * @param weird_sampling_duration New weird sampling duration.
     */
    void SetWeirdSamplingDuration(double weird_sampling_duration) {
        this->weird_sampling_duration = weird_sampling_duration;
    }

private:
    void DoLog(const char* prefix, EventHandlerPtr event, FILE* out, Connection* conn, ValPList* addl, bool location,
               bool time, const char* postfix, const char* fmt, va_list ap) __attribute__((format(printf, 10, 0)));

    // WeirdHelper doesn't really have to be variadic, but it calls DoLog
    // and that takes va_list anyway.
    void WeirdHelper(EventHandlerPtr event, ValPList vl, const char* fmt_name, ...)
        __attribute__((format(printf, 4, 5)));
    void UpdateWeirdStats(const char* name);
    inline bool WeirdOnSamplingWhiteList(const char* name) { return weird_sampling_whitelist.contains(name); }
    inline bool WeirdOnGlobalList(const char* name) { return weird_sampling_global_list.contains(name); }
    bool PermitNetWeird(const char* name);
    bool PermitFlowWeird(const char* name, const IPAddr& o, const IPAddr& r);
    bool PermitExpiredConnWeird(const char* name, const RecordVal& conn_id);

    enum class PermitWeird : uint8_t { Allow, Deny, Unknown };
    PermitWeird CheckGlobalWeirdLists(const char* name);

    bool EmitToStderr(bool flag);

    int errors;
    int in_error_handler;
    bool via_events;
    bool syslog_open;
    bool info_to_stderr;
    bool warnings_to_stderr;
    bool errors_to_stderr;
    bool abort_on_scripting_errors = false;

    std::list<std::pair<const detail::Location*, const detail::Location*>> locations;

    uint64_t weird_count;
    WeirdCountMap weird_count_by_type;
    WeirdCountMap net_weird_state;
    WeirdFlowMap flow_weird_state;
    WeirdConnTupleMap expired_conn_weird_state;

    WeirdSet weird_sampling_whitelist;
    WeirdSet weird_sampling_global_list;
    uint64_t weird_sampling_threshold;
    uint64_t weird_sampling_rate;
    double weird_sampling_duration;

    bool ignore_deprecations;
};

/**
 * Helper class pushing the frame's call location onto
 * the reporter's location stack, associating the caller's
 * location with subsequent output.
 *
 * The frame's location must remain valid until the object
 * is destroyed.
 */
class ScriptLocationScope {
public:
    ScriptLocationScope(const zeek::detail::Frame* frame);
    ~ScriptLocationScope();
};

ZEEK_EXTERN_DATA Reporter* reporter;

} // namespace zeek
