/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.
 */
#ifndef THRIFT_ASYNC_TEVENTHANDLER_H_
#define THRIFT_ASYNC_TEVENTHANDLER_H_ 1

#include "thrift/lib/cpp/thrift_config.h"
#include "thrift/lib/cpp/async/TEventUtil.h"
#include <boost/noncopyable.hpp>
#include <stddef.h>

namespace apache { namespace thrift { namespace async {

class TEventBase;

/**
 * The TEventHandler class is used to asynchronously wait for events on a file
 * descriptor.
 *
 * Users that wish to wait on I/O events should derive from TEventHandler and
 * implement the handlerReady() method.
 */
class TEventHandler : private boost::noncopyable {
 public:
  enum EventFlags {
    NONE = 0,
    READ = EV_READ,
    WRITE = EV_WRITE,
    READ_WRITE = (READ | WRITE),
    PERSIST = EV_PERSIST
  };

  /**
   * Create a new TEventHandler object.
   *
   * @param eventBase  The TEventBase to use to drive this event handler.
   *                   This may be NULL, in which case the TEventBase must be
   *                   set separately using initHandler() or attachEventBase()
   *                   before the handler can be registered.
   * @param fd         The file descriptor that this TEventHandler 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 TEventHandler(TEventBase* eventBase = NULL, int fd = -1);

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

  /**
   * handlerReady() is invoked when the handler is ready.
   *
   * @param events  A bitset indicating the events that are ready.
   */
  virtual void handlerReady(uint16_t events) THRIFT_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 TEventUtil::isEventRegistered(&event_);
  }

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

  /**
   * Detach the handler from its TEventBase.
   *
   * 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 TEventBase by calling attachEventBase().
   *
   * This method must be called from the current TEventBase'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(int fd);

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

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

  /**
   * Register the handler as an internal event.
   *
   * This event will not count as an active event for determining if the
   * TEventBase loop has more events to process.  The TEventBase loop runs
   * only as long as there are active TEventHandlers, however "internal" event
   * handlers are not counted.  Therefore this event handler will not prevent
   * TEventBase 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
   * TEventBase code.  This API is not guaranteed to remain stable or portable
   * in the future.
   */
  bool registerInternalHandler(uint16_t events) {
    return registerImpl(events, true);
  }

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

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

  struct event event_;
};

}}} // apache::thrift::async

#endif // THRIFT_ASYNC_TEVENTHANDLER_H_