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

#pragma once

#include <map>
#include <string>
#include <vector>

#include "zeek/Flare.h"
#include "zeek/iosource/IOSource.h"

struct timespec;
struct kevent;

namespace zeek {
namespace iosource {

class PktSrc;
class PktDumper;

/**
 * Manager class for IO sources. This handles all of the polling of sources
 * in the main loop.
 */
class Manager {
public:
    struct ReadySource {
        IOSource* src = nullptr;
        int fd = -1;
        int flags = 0;
    };

    using ReadySources = std::vector<ReadySource>;

    /**
     * Constructor.
     */
    Manager();

    /**
     * Destructor.
     */
    virtual ~Manager();

    /**
     * Initializes some extra fields that can't be done during the
     * due to dependencies on other objects being initialized first.
     */
    void InitPostScript();

    /**
     * Registers an IOSource with the manager. If the source is already
     * registered, the method will update its *dont_count* value but not
     * do anything else.
     *
     * @param src The source. The manager takes ownership.
     *
     * @param dont_count If true, this source does not contribute to the
     * number of IOSources returned by Size().  The effect is that if all
     * sources except for the non-counting ones have gone dry, processing
     * will shut down.
     */
    void Register(IOSource* src, bool dont_count = false, bool manage_lifetime = true);

    /**
     * Returns the number of registered and still active sources,
     * excluding those that are registered as \a dont_count.
     */
    int Size() const { return sources.size() - dont_counts; }

    /**
     * Returns total number of sources including dont_counts;
     */
    int TotalSize() const { return sources.size(); }

    /**
     * Returns the registered PktSrc. If not source is registered yet,
     * returns a nullptr.
     */
    PktSrc* GetPktSrc() const { return pkt_src; }

    /**
     * Terminate all processing immediately by removing all sources (and
     * therefore now returning a Size() of zero).
     */
    void Terminate() { RemoveAll(); }

    /**
     * Opens a new packet source.
     *
     * @param path The interface or file name, as one would give to zeek \c -i.
     *
     * @param is_live True if \a path represents a live interface, false
     * for a file.
     *
     * @return The new packet source, or null if an error occurred.
     */
    PktSrc* OpenPktSrc(const std::string& path, bool is_live);

    /**
     * Opens a new packet dumper.
     *
     * @param path The file name to dump into.
     *
     * @param append True to append if \a path already exists.
     *
     * @return The new packet dumper, or null if an error occurred.
     */
    PktDumper* OpenPktDumper(const std::string& path, bool append);

    /**
     * Finds the sources that have data ready to be processed.
     *
     * @param ready A vector used to return the set of sources that are ready.
     */
    void FindReadySources(ReadySources* ready);

    /**
     * Registers a file descriptor and associated IOSource with the manager
     * to be checked during FindReadySources. This will register the file
     * descriptor to check for read events.
     *
     * @param fd A file descriptor pointing at some resource that should be
     * checked for readiness.
     * @param src The IOSource that owns the file descriptor.
     * @param flags A combination of values from IOSource::ProcessFlags for
     * which modes we should register for this file descriptor.
     */
    bool RegisterFd(int fd, IOSource* src, int flags = IOSource::READ);

    /**
     * Unregisters a file descriptor from the FindReadySources checks.
     */
    bool UnregisterFd(int fd, IOSource* src, int flags = IOSource::READ);

    /**
     * Forces the poll in FindReadySources to wake up immediately. This method
     * is called during RegisterFd and UnregisterFd since those methods cause
     * changes to the active set of file descriptors.
     */
    void Wakeup(std::string_view where);

private:
    /**
     * Internal data structure for managing registered IOSources.
     */
    struct Source {
        IOSource* src = nullptr;
        bool dont_count = false;
        bool manage_lifetime = false;
    };

    /**
     * Calls the appropriate poll method to gather a set of IOSources that are
     * ready for processing.
     *
     * @param ready a vector used to return the ready sources.
     * @param timeout the value to be used for the timeout of the poll. This
     * should be a value relative to the current network time, not an
     * absolute time value. This may be zero to cause an infinite timeout or
     * -1 to force a very short timeout.
     * @param timeout_src The source associated with the current timeout value.
     * This is typically a timer manager object.
     */
    void Poll(ReadySources* ready, double timeout, IOSource* timeout_src);

    /**
     * Converts a double timeout value into a timespec struct used for calls
     * to kevent().
     */
    void ConvertTimeout(double timeout, struct timespec& spec);

    /**
     * Specialized registration method for packet sources.
     */
    void Register(PktSrc* src);

    void RemoveAll();

    /**
     * Reap a closed IO source.
     *
     * Reaping involves calling IOSource::Done() on the underlying IOSource,
     * freeing it if Source.manage_lifetime is \c true, updating \c dont_counts
     * and freeing \a src, making it invalid.
     *
     * The caller ensures \a src is removed from Manager.sources.
     *
     * @param src The source to reap.
     */
    void ReapSource(Source* src);

    class WakeupHandler final : public IOSource {
    public:
        WakeupHandler();
        ~WakeupHandler() override;

        /**
         * Tells the handler to wake up the loop by firing the flare.
         *
         * @param where a string denoting where this ping was called from. Used
         * for debugging output.
         */
        void Ping(std::string_view where);

        // IOSource API methods
        void Process() override;
        const char* Tag() override { return "WakeupHandler"; }
        double GetNextTimeout() override { return -1; }

    private:
        zeek::detail::Flare flare;
    };

    using SourceList = std::vector<Source*>;
    SourceList sources;

    using PktDumperList = std::vector<PktDumper*>;
    PktDumperList pkt_dumpers;

    PktSrc* pkt_src = nullptr;

    int dont_counts = 0;
    int zero_timeout_count = 0;
    WakeupHandler* wakeup = nullptr;
    int poll_counter = 0;
    int poll_interval = 0; // Set in InitPostScript() based on const value.

    int event_queue = -1;
    std::map<int, IOSource*> fd_map;
    std::map<int, IOSource*> write_fd_map;

    // This is only used for the output of the call to kqueue in FindReadySources().
    // The actual events are stored as part of the queue.
    std::vector<struct kevent> events;
};

} // namespace iosource

extern iosource::Manager* iosource_mgr;

} // namespace zeek
