/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <cstddef>

#include <glog/logging.h>

#include <folly/io/async/EventBaseBackendBase.h>
#include <folly/io/async/EventUtil.h>
#include <folly/net/NetworkSocket.h>
#include <folly/portability/Event.h>

namespace folly {

class EventBase;

/**
 * The EventHandler class is used to asynchronously wait for events on a file
 * descriptor.
 *
 * Users that wish to wait on I/O events should derive from EventHandler and
 * implement the handlerReady() method.
 */
class EventHandler {
 public:
  enum EventFlags {
    NONE = 0,
    READ = EV_READ,
    WRITE = EV_WRITE,
    READ_WRITE = (READ | WRITE),
    PERSIST = EV_PERSIST,
// Temporary flag until EPOLLPRI is upstream on libevent.
#ifdef EV_PRI
    PRI = EV_PRI,
#endif
  };

  /**
   * Create a new EventHandler object.
   *
   * @param eventBase  The EventBase to use to drive this event handler.
   *                   This may be nullptr, in which case the EventBase must be
   *                   set separately using initHandler() or attachEventBase()
   *                   before the handler can be registered.
   * @param fd         The file descriptor that this EventHandler will
   *                   monitor.  This may be -1, in which case the file
   *                   descriptor must be set separately using initHandler() or
   *                   changeHandlerFD() before the handler can be registered.
   */
  explicit EventHandler(
      EventBase* eventBase = nullptr, NetworkSocket fd = NetworkSocket());

  EventHandler(const EventHandler&) = delete;
  EventHandler& operator=(const EventHandler&) = delete;

  /**
   * EventHandler destructor.
   *
   * The event will be automatically unregistered if it is still registered.
   */
  virtual ~EventHandler();

  /**
   * handlerReady() is invoked when the handler is ready.
   *
   * @param events  A bitset indicating the events that are ready.
   */
  virtual void handlerReady(uint16_t events) noexcept = 0;

  /**
   * Register the handler.
   *
   * If the handler is already registered, the registration will be updated
   * to wait on the new set of events.
   *
   * @param events   A bitset specifying the events to monitor.
   *                 If the PERSIST bit is set, the handler will remain
   *                 registered even after handlerReady() is called.
   *
   * @return Returns true if the handler was successfully registered,
   *         or false if an error occurred.  After an error, the handler is
   *         always unregistered, even if it was already registered prior to
   *         this call to registerHandler().
   */
  bool registerHandler(uint16_t events) { return registerImpl(events, false); }

  /**
   * Unregister the handler, if it is registered.
   */
  void unregisterHandler();

  /**
   * Returns true if the handler is currently registered.
   */
  bool isHandlerRegistered() const { return event_.isEventRegistered(); }

  /**
   * Attach the handler to a EventBase.
   *
   * This may only be called if the handler is not currently attached to a
   * EventBase (either by using the default constructor, or by calling
   * detachEventBase()).
   *
   * This method must be invoked in the EventBase's thread.
   */
  void attachEventBase(EventBase* eventBase);

  /**
   * Detach the handler from its EventBase.
   *
   * This may only be called when the handler is not currently registered.
   * Once detached, the handler may not be registered again until it is
   * re-attached to a EventBase by calling attachEventBase().
   *
   * This method must be called from the current EventBase's thread.
   */
  void detachEventBase();

  /**
   * Change the file descriptor that this handler is associated with.
   *
   * This may only be called when the handler is not currently registered.
   */
  void changeHandlerFD(NetworkSocket fd);

  /**
   * Attach the handler to a EventBase, and change the file descriptor.
   *
   * This method may only be called if the handler is not currently attached to
   * a EventBase.  This is primarily intended to be used to initialize
   * EventHandler objects created using the default constructor.
   */
  void initHandler(EventBase* eventBase, NetworkSocket fd);

  /**
   * Return the set of events that we're currently registered for.
   */
  uint16_t getRegisteredEvents() const {
    return (isHandlerRegistered()) ? (uint16_t)(event_.eb_ev_events()) : 0u;
  }

  /**
   * Register the handler as an internal event.
   *
   * This event will not count as an active event for determining if the
   * EventBase loop has more events to process.  The EventBase loop runs
   * only as long as there are active EventHandlers, however "internal" event
   * handlers are not counted.  Therefore this event handler will not prevent
   * EventBase loop from exiting with no more work to do if there are no other
   * non-internal event handlers registered.
   *
   * This is intended to be used only in very rare cases by the internal
   * EventBase code.  This API is not guaranteed to remain stable or portable
   * in the future.
   */
  bool registerInternalHandler(uint16_t events) {
    return registerImpl(events, true);
  }

  bool isPending() const;

  void setEventCallback(EventReadCallback* cb) { event_.setCallback(cb); }

  void setEventCallback(EventRecvmsgCallback* cb) { event_.setCallback(cb); }

  void setRecvmsgMultishotCallback(EventRecvmsgMultishotCallback* cb) {
    event_.setCallback(cb);
  }

  void resetEventCallback() { event_.resetCallback(); }

  /*
   * If supported by the backend updates the event to be edge-triggered.
   * Returns true iff the update was successful.
   *
   * This should only be used for already registered events (e.g. after
   * registerHandler/registerInternalHandler calls). Calling any other method
   * on the EventHandler may reset this flag.
   * This can be useful to avoid read calls with eventfds.
   */
  bool setEdgeTriggered() { return event_.setEdgeTriggered(); }

  /*
   * Make an event active.
   */
  bool activateEvent(int res) { return event_.eb_event_active(res); }

 private:
  bool registerImpl(uint16_t events, bool internal);
  void ensureNotRegistered(const char* fn);

  void setEventBase(EventBase* eventBase);

  static void libeventCallback(libevent_fd_t fd, short events, void* arg);

  EventBaseBackendBase::Event event_;
  EventBase* eventBase_;
};

} // namespace folly
