/*
 * 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_TASYNCTIMEOUT_H_
#define THRIFT_ASYNC_TASYNCTIMEOUT_H_ 1

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

namespace apache { namespace thrift { namespace async {

class TEventBase;

/**
 * TAsyncTimeout is used to asynchronously wait for a timeout to occur.
 */
class TAsyncTimeout : private boost::noncopyable {
 public:
  enum InternalEnum {
    INTERNAL,
    NORMAL
  };

  /**
   * Create a new TAsyncTimeout object, driven by the specified TEventBase.
   */
  explicit TAsyncTimeout(TEventBase* eventBase);

  /**
   * Create a new internal TAsyncTimeout object.
   *
   * Internal timeouts are like regular timeouts, but will not stop the
   * TEventBase loop from exiting if the only remaining events are internal
   * timeouts.
   *
   * This is useful for implementing fallback timeouts to abort the TEventBase
   * loop if the other events have not been processed within a specified time
   * period: if the event loop takes too long the timeout will fire and can
   * stop the event loop.  However, if all other events complete, the event
   * loop will exit even though the internal timeout is still installed.
   */
  TAsyncTimeout(TEventBase* eventBase, InternalEnum internal);

  /**
   * Create a new TAsyncTimeout object, not yet assigned to a TEventBase.
   *
   * attachEventBase() must be called prior to scheduling the timeout.
   */
  TAsyncTimeout();

  /**
   * TAsyncTimeout destructor.
   *
   * The timeout will be automatically cancelled if it is running.
   */
  virtual ~TAsyncTimeout();

  /**
   * timeoutExpired() is invoked when the timeout period has expired.
   */
  virtual void timeoutExpired() THRIFT_NOEXCEPT = 0;

  /**
   * Schedule the timeout to fire in the specified number of milliseconds.
   *
   * After the specified number of milliseconds has elapsed, timeoutExpired()
   * will be invoked by the TEventBase's main loop.
   *
   * If the timeout is already running, it will be rescheduled with the
   * new timeout value.
   *
   * @param milliseconds  The timeout duration, in milliseconds.
   *
   * @return Returns true if the timeout was successfully scheduled,
   *         and false if an error occurred.  After an error, the timeout is
   *         always unscheduled, even if scheduleTimeout() was just
   *         rescheduling an existing timeout.
   */
  bool scheduleTimeout(uint32_t milliseconds);

  /**
   * Cancel the timeout, if it is running.
   */
  void cancelTimeout();

  /**
   * Returns true if the timeout is currently scheduled.
   */
  bool isScheduled() {
    return TEventUtil::isEventRegistered(&event_);
  }

  /**
   * Attach the timeout to a TEventBase.
   *
   * This may only be called if the timeout 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.
   *
   * The internal parameter specifies if this timeout should be treated as an
   * internal event.  TEventBase::loop() will return when there are no more
   * non-internal events remaining.
   */
  void attachEventBase(TEventBase* eventBase,
                       InternalEnum internal = NORMAL);

  /**
   * Detach the timeout from its TEventBase.
   *
   * This may only be called when the timeout is not running.
   * Once detached, the timeout may not be scheduled 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();

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

  struct event event_;

  /*
   * In debug builds, store a pointer to the TEventBase.  We only use this
   * for some assert() statements, to make sure that TAsyncTimeout is always
   * used from the correct thread.
   */
#ifndef NDEBUG
  TEventBase* eventBase_;
#endif
};

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

#endif // THRIFT_ASYNC_TASYNCTIMEOUT_H_